【kotlin】- delay函數(shù)實(shí)現(xiàn)原理

簡(jiǎn)介

這片文章主要講解kotlindelay函數(shù)的實(shí)現(xiàn)原理谦炬,delay是一個(gè)掛起函數(shù)。kotlin攜程使用過(guò)程中,經(jīng)常使用到掛起函數(shù)黔州,在我學(xué)習(xí)kotlin攜程的時(shí)候耍鬓,一些現(xiàn)象讓我很是困惑,所以打算從源碼角度來(lái)逐一分析流妻。

說(shuō)明

在分析delay源碼實(shí)現(xiàn)過(guò)程中牲蜀,由于對(duì)kotlin有些語(yǔ)法還不是很熟悉,所以并不會(huì)把每一步將得很透徹绅这,只會(huì)梳理一個(gè)大致的流程涣达,如果講解有誤的地方,歡迎指出证薇。

例子先行

fun main() = runBlocking {
    println("${treadName()}======start")
    launch {
        println("${treadName()}======delay 1s  start")
        delay(1000)
        println("${treadName()}======delay 1s end")
    }

    println("${treadName()}======delay 3s start")
    delay(3000)
    println("${treadName()}======delay 3s end")
    // 延遲度苔,保活進(jìn)程
    Thread.sleep(500000)
}

輸出如下:

main======start
main======delay 3s start
main======delay 1s  start
main======delay 1s end
main======delay 3s end

根據(jù)日志可以看出:

  1. 日志輸出環(huán)境是在主線程浑度。
  2. 執(zhí)行3s延遲函數(shù)后寇窑,切換到了launch攜程體執(zhí)行。
  3. delay掛起函數(shù)恢復(fù)后執(zhí)行各自的打印函數(shù)箩张。

\color{blue}{疑問(wèn)}
如果真像打印日志輸出一樣甩骏,所以的操作都是在一個(gè)線程(主線程)完成,那么問(wèn)題來(lái)了伏钠。第一:按照J(rèn)ava線程知識(shí)横漏,單線程執(zhí)行是按照順序的,是單條線的熟掂。那么不管delay里是何等騷操作缎浇,只要沒(méi)有重新起線程,應(yīng)該不能夠像上面輸入的那樣吧赴肚,你說(shuō)sleep素跺,wait,如果你這么想,那么你可以去補(bǔ)一補(bǔ)Java多線程基礎(chǔ)知識(shí)了誉券。猜想1. 難得真有什么我不知道的騷操作可以在一個(gè)線程里面同時(shí)執(zhí)行delay和其它代碼指厌,真像很多人說(shuō)的,攜程性能很好踊跟,使用掛起函數(shù)可以不用啟動(dòng)新的線程踩验,就可以異步執(zhí)行,那真的就很不錯(cuò)商玫。2. delay啟動(dòng)了新的線程箕憾,上面的現(xiàn)象只不過(guò)是進(jìn)行了線程切換,那么如果多次調(diào)用 delay那么豈不是要?jiǎng)?chuàng)建很多線程拳昌,這性能問(wèn)題和資源問(wèn)題怎么解決袭异。3. delay基于某種任務(wù)調(diào)度策略。

delay源碼

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
}

cancellable是一個(gè)CancellableContinuationImpl對(duì)象炬藤,執(zhí)行 block(cancellable)御铃,回到下面函數(shù)碴里。

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

看一下cont.context.delayget方法

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay

如果get(ContinuationInterceptor)Delay類(lèi)型對(duì)象,那么直接返回該對(duì)象上真,如果不是返回DefaultDelay變量咬腋,看一下DefaultDelay初始化可以知道,它是一個(gè)DefaultExecutor對(duì)象睡互,繼承了EventLoopImplBase類(lèi)帝火。

runBlocking執(zhí)行過(guò)程中有這樣一行代碼createCoroutineUnintercepted(receiver, completion).intercepted()會(huì)被ContinuationInterceptor進(jìn)行包裝。所以上面cont.context.delay返回的就是被包裝的攜程體上下文湃缎。

查看scheduleResumeAfterDelay方法。

    public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        val timeNanos = delayToNanos(timeMillis)
        if (timeNanos < MAX_DELAY_NS) {
            val now = nanoTime()
            DelayedResumeTask(now + timeNanos, continuation).also { task ->
                continuation.disposeOnCancellation(task)
                schedule(now, task)
            }
        }
    }

創(chuàng)建DelayedResumeTask對(duì)象蠢壹,在also執(zhí)行相關(guān)計(jì)劃任務(wù)嗓违,看一下schedule方法。

    public fun schedule(now: Long, delayedTask: DelayedTask) {
        when (scheduleImpl(now, delayedTask)) {
            SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark()
            SCHEDULE_COMPLETED -> reschedule(now, delayedTask)
            SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed
            else -> error("unexpected result")
        }
    }

這里返回SCHEDULE_OK,執(zhí)行unpark函數(shù)图贸,這里用到了Java提供的LockSupport線程操作相關(guān)知識(shí)蹂季。

讀取線程

  val thread = thread
  • 如果delay是當(dāng)前攜程的上下文
    那么把延時(shí)任務(wù)加入到隊(duì)列后,那么又是怎么達(dá)到線程延遲呢疏日〕ソ啵回到runBlocking執(zhí)行流程,會(huì)執(zhí)行coroutine.joinBlocking()這樣一行代碼沟优。

      fun joinBlocking(): T {
          registerTimeLoopThread()
          try {
              eventLoop?.incrementUseCount()
              try {
                  while (true) {
                      @Suppress("DEPRECATION")
                      if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
                      val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
                      // note: process next even may loose unpark flag, so check if completed before parking
                      if (isCompleted) break
                      parkNanos(this, parkNanos)
                  }
              } finally { // paranoia
                  eventLoop?.decrementUseCount()
              }
          } finally { // paranoia
              unregisterTimeLoopThread()
          }
          // now return result
          val state = this.state.unboxState()
          (state as? CompletedExceptionally)?.let { throw it.cause }
          return state as T
      }
    

    執(zhí)行:

     val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
    

    看一下processNextEvent

      override fun processNextEvent(): Long {
          // unconfined events take priority
          if (processUnconfinedEvent()) return 0
          // queue all delayed tasks that are due to be executed
          val delayed = _delayed.value
          if (delayed != null && !delayed.isEmpty) {
              val now = nanoTime()
              while (true) {         
                  delayed.removeFirstIf {
                      if (it.timeToExecute(now)) {
                          enqueueImpl(it)
                      } else
                          false
                  } ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
              }
          }
          // then process one event from queue
          val task = dequeue()
          if (task != null) {
              task.run()
              return 0
          }
          return nextTime
      }
    

    從延遲隊(duì)列取任務(wù)

    val delayed = _delayed.value
    

    掛起當(dāng)前線程

    parkNanos(this, parkNanos)
    

    這里是一個(gè)while循環(huán)涕滋,當(dāng)掛起時(shí)間到,線程喚醒挠阁,繼續(xù)從任務(wù)隊(duì)列中取任務(wù)執(zhí)行宾肺。如果還是延遲任務(wù),這根據(jù)當(dāng)前時(shí)間點(diǎn)侵俗,計(jì)算線程需要掛起的時(shí)間锨用,這也是為什么多個(gè)延遲任務(wù)好像是同時(shí)執(zhí)行的。

  • 如果delay是DefaultExecutor
    比如這個(gè)例子:攜程上下文沒(méi)有像CoroutineStart.DEFAULT那樣進(jìn)行包裝隘谣。

    fun main() {
      GlobalScope.launch(start = CoroutineStart.UNDISPATCHED){
           println("${treadName()}======我開(kāi)始執(zhí)行了~")
           delay(1000)
            println("${treadName()}======全局?jǐn)y程~")
        }
        println("${treadName()}======我要睡覺(jué)~")
        Thread.sleep(3000)
    }
    

    然后調(diào)用DefaultExecutor類(lèi)中thread的get方法:

      override val thread: Thread
          get() = _thread ?: createThreadSync()
    

    看一下createThreadSync函數(shù)

      private fun createThreadSync(): Thread {
          return _thread ?: Thread(this, THREAD_NAME).apply {
              _thread = this
              isDaemon = true
              start()
          }
      }
    

    創(chuàng)建一個(gè)叫"kotlinx.coroutines.DefaultExecutor的新線程增拥,并且開(kāi)始運(yùn)行。這時(shí)候會(huì)執(zhí)行DefaultExecutor中的run方法寻歧。在run方法中有這樣一行代碼:

    parkNanos(this, parkNanos)
    

    點(diǎn)進(jìn)去看看:

    internal inline fun parkNanos(blocker: Any, nanos: Long) {
      timeSource?.parkNanos(blocker, nanos) ?: LockSupport.parkNanos(blocker,   nanos)
    }
    

    調(diào)用Java提供的LockSupport.parkNanos(blocker, nanos)方法掌栅,阻塞當(dāng)前線程,實(shí)現(xiàn)掛起熄求,當(dāng)達(dá)到阻塞的時(shí)間渣玲,恢復(fù)線程執(zhí)行。

查看進(jìn)行中線程情況方法

fun main() {
    println("${treadName()}======doSuspendTwo")
    Thread.sleep(500000)
}

運(yùn)行main弟晚,通過(guò)命令jps找到對(duì)應(yīng)Java進(jìn)程(沒(méi)有特別指定忘衍,進(jìn)程名為文件名)號(hào)逾苫。

...
3406 KotlinCoreutinesSuspendKt
...

執(zhí)行jstack 進(jìn)程號(hào)查看進(jìn)程對(duì)應(yīng)的線程資源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枚钓,一起剝皮案震驚了整個(gè)濱河市铅搓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搀捷,老刑警劉巖星掰,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嫩舟,居然都是意外死亡氢烘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)家厌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)播玖,“玉大人,你說(shuō)我怎么就攤上這事饭于∈裉ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵掰吕,是天一觀的道長(zhǎng)果覆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)殖熟,這世上最難降的妖魔是什么局待? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮菱属,結(jié)果婚禮上燎猛,老公的妹妹穿的比我還像新娘。我一直安慰自己照皆,他們只是感情好重绷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著膜毁,像睡著了一般昭卓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘟滨,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天候醒,我揣著相機(jī)與錄音,去河邊找鬼杂瘸。 笑死倒淫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的败玉。 我是一名探鬼主播敌土,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼镜硕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了返干?” 一聲冷哼從身側(cè)響起兴枯,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矩欠,沒(méi)想到半個(gè)月后财剖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癌淮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年躺坟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乳蓄。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞳氓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栓袖,到底是詐尸還是另有隱情,我是刑警寧澤店诗,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布裹刮,位于F島的核電站,受9級(jí)特大地震影響庞瘸,放射性物質(zhì)發(fā)生泄漏捧弃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一擦囊、第九天 我趴在偏房一處隱蔽的房頂上張望违霞。 院中可真熱鬧,春花似錦瞬场、人聲如沸买鸽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眼五。三九已至,卻和暖如春彤灶,著一層夾襖步出監(jiān)牢的瞬間看幼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工幌陕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诵姜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓搏熄,卻偏偏與公主長(zhǎng)得像棚唆,于是被迫代替她去往敵國(guó)和親暇赤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 這篇文章大部分內(nèi)容來(lái)自:https://github.com/Kotlin/kotlinx.coroutines/...
    Jason__Ding閱讀 19,918評(píng)論 9 55
  • 在今年的三月份瑟俭,我因?yàn)樾枰獮轫?xiàng)目搭建一個(gè)新的網(wǎng)絡(luò)請(qǐng)求框架開(kāi)始接觸 Kotlin 協(xié)程翎卓。那時(shí)我司項(xiàng)目中同時(shí)存在著兩種...
    業(yè)志陳閱讀 1,028評(píng)論 0 5
  • 輕量級(jí)線程:協(xié)程 在常用的并發(fā)模型中失暴,多進(jìn)程、多線程微饥、分布式是最普遍的逗扒,不過(guò)近些年來(lái)逐漸有一些語(yǔ)言以first-c...
    Tenderness4閱讀 6,357評(píng)論 2 10
  • package com.example.kotlin_demo import androidx.appcompat...
    多一點(diǎn)童真閱讀 850評(píng)論 0 0
  • 本文為協(xié)程的開(kāi)篇作,作者目前對(duì)協(xié)程的理解仍存在一些疑問(wèn)欠橘,歡迎批評(píng)指正矩肩。 概念 ?些 API 啟動(dòng)?時(shí)間運(yùn)?的操作(...
    wanderingGuy閱讀 1,556評(píng)論 1 3