Kotlin學習系列之:協程(一)

1.協程:Coroutine

2.如何去理解協程: 可以視爲輕量級的線程

  • 我們可以回顧一下什麼是線程。從操作系統原理的角度來講,進程是資源分配的基本單位,而線程是調度的基本單位,也就是說線程實際上是系統級別的,它運行與否是由操作系統決定的。從Java語言層面上講,我們可以通過new Thread().start這種形式去啓動一個線程,我們可以查看Thread類的源代碼:

     public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
    private native void start0();
    

我們可以看到start0這個native方法,也就是說開啓一個線程實際上是需要jvm同底層操作系統進行打交道的。那麼我們可以用一句話總結就是:線程是系統級別的,它的開銷是巨大的,比較耗費計算機資源,而協程是輕量級的,開銷小。

  • 既然協程如此輕便,那麼是不是意味着我們可以拋棄線程了呢?當然不是,協程是依賴於線程的,不過協程掛起時是不會阻塞線程的,而一個線程中可以創建任意個協程

  • 協程的作用:爲了簡化異步編程。回想Java中,我們需要線程同步或者接口回調的方式來實現異步編程,實際上很繁瑣。而像Flutter中,或者說Dart語言本身,提供了async、await等關鍵字來實現異步編程。Kotlin中就提供了協程來簡化異步編程

3.編寫第一個協程代碼:

fun main() {

    GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    Thread.sleep(2000)

    println("welcome")
}

輸出結果:

hello
(等待1s)
world
(等待1s)
welcome

這段代碼我們待會兒再來解釋,我們再來看一個熟悉的例子:

fun main() {

    Thread{
        Thread.sleep(1000)
        println("world")
    }.start()

    println("hello")
    Thread.sleep(2000)
    println("welcome")

}

輸出結果同上面一樣,等待時間也是一樣的。後面這段代碼易懂,我們按照時間順序來解釋:

  • 0s: 啓動一個子線程;子線程開始休眠;主線程打印hello;主線程開始休眠。
  • 1s:子線程到達休眠時間,喚醒後打印world
  • 2s:主線程被喚醒,打印welcome

理解到這,我們再來對比第一段協程代碼:

  • 如何創建一個協程:GlobalScope.launch{...} 是不是和創建一個子線程的形式類似
  • launch後面的代碼塊是異步執行的,並且不會阻塞線程

同樣,我們按照時間順序來解釋協程代碼:

  • 0s:創建一個協程;執行delay延遲;主線程打印hello;主線程開始休眠
  • 1s:協程的delay時間已到,打印world
  • 2s:主線程被喚醒,打印welcome

現在,我們針對第一段協程代碼,稍微改動一下主線程的休眠時間,將2000毫秒改爲500毫秒,再來看一看運行結果:

hello
休眠0.5s
welcome

好了,到這程序就運行結束了,"world"永遠也不會有機會輸出。爲什麼呢? - 0s:創建一個協程;執行delay延遲;主線程打印hello;主線程開始休眠 - 0.5s:主線程被喚醒,打印welcome;主線程運行結束,程序退出。 程序都已經退出了,那麼協程當然也就不存在了。

感興趣你還可以針對第二段線程代碼進行同樣的修改,看看結果有什麼不同,這裏就不再詳述了。

4.GlobalScope.launch{}

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job]

這是launch方法的描述,翻譯過來就是:它會在不阻塞當前線程的前提下去啓動一個新的協程,然後返回一個關聯該協程的Job類型的引用。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

另外從源代碼的角度上可以看出:launch是CoroutineScope的一個擴展方法,並且返回值是一個Job類型

  • CoroutineScope:

    /**
     * Defines a scope for new coroutines. Every coroutine builder
     * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
     * to automatically propagate both context elements and cancellation.
    */
    public interface CoroutineScope {
    /**
         * The context of this scope.
         * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
         * Accessing this property in general code is not recommended for any purposes except accessing [Job] instance for advanced usages.
         *
         * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
         */
        public val coroutineContext: CoroutineContext
    }
    

    用來定義協程的作用範圍,有個概念就行。

  • GlobalScope:

    public object GlobalScope : CoroutineScope {
        /**
         * Returns [EmptyCoroutineContext].
         */
        override val coroutineContext: CoroutineContext
            get() = EmptyCoroutineContext
    }
    

可以看出,GlobalScope是一個實現了CoroutineScope接口的對象,並且重寫coroutineContext的get方法。

當然,CoroutineScope.launch{}只是啓動協程的方式之一,Kotlin還提供了其他啓動協程的方式,這些方式或者叫方法有一個名字,叫做協程建造器(coroutine builder)。我們將在接下來的篇章去進行介紹

5.補充:

我們剛剛在創建一個線程時,使用瞭如下代碼:

Thread{
    ....
}.start()

實際上Kotlin中提供一種更爲簡便的方式:

thread {
    ...
}

這個thread實際上一個頂級函數(top-level function),在kotlin.concurrent包下,它的實現如下:

public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread {
    val thread = object : Thread() {
        public override fun run() {
            block()
        }
    }
    if (isDaemon)
        thread.isDaemon = true
    if (priority > 0)
        thread.priority = priority
    if (name != null)
        thread.name = name
    if (contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if (start)
        thread.start()
    return thread
}

實現也是一看就懂,就是Kotlin爲了開發者編碼方便而做了一層封裝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章