kotlin09.协程初识

125 阅读4分钟

协程初识

协程定义

官方定义为轻量级的线程. 传统线程在运行时,具有运行时数据,当发生线程切换时,需要将线程的栈数据和指令计数器等信息保存到进程的pcb中,发生用户态跟内核态的切换.一般情况下,一次切换的耗时大约在3.5us左右.

协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。

协程为协同任务提供了一种运行时抽象,这种抽象非常适合于协同多任务调度和数据流处理。因为用户态线程切换代价比内核态线程小,协程成为了一种轻量级的多任务模型

hello world

fun main() {
    var job = GlobalScope.launch {
        delay(1000)
        println("world")
        println(Thread.currentThread().name)
    }
    print("hello,")
    Thread.sleep(2000)
}

//输出结果为 hello,world
DefaultDispatcher-worker-1

从输出可以看出

  • GlobalScope.launch开了一个名为DefaultDispatcher-worker-x的线程.
  • GlobalScope.launch启动了一个作用域为GlobalScope的Job,并将Job加入到了作用域为GlobalScope的任务队列,并挂起1000ms.
  • 由于主线程执行完毕后会终止程序,因为最后sleep(2000),让协程Job得以执行.

关于delay和sleep

  • delay是协程中专有的方法,将协程挂起,并不释放cpu资源.协程所在的线程会去继续查找并执行其它的任务(Job).
  • Thread.sleep()将线程暂停执行,进入(TIMED_WAITING)等待状态,线程在sleep过程中不会释放它已经获得的任意的monitor和lock等资源,但是会释放cpu资源(调度算法在睡眠时间内不会调度到该线程).睡眠结束以后,并不是直接回到运行态,而是进入就绪队列,要等到其他进程放弃时间片后才能重新进入运行态

runBlocking

  • runBlocking将一个线程包装成协程,内部实现会将线程传递给BlockingCoroutine
  • 它的默认作用域是CoroutineScope
  • job.join作用与thread.join类似,等待任务执行结果.
fun main() = runBlocking {
    val job = GlobalScope.launch {
        delay(1000)
        println("world")
    }
    print("hello,")
    job.join() // 等待子协程执行结束
    println("all coroutine done !")
}

//输出
hello,world
all coroutine done !

默认作用域CoroutineScope

  • lauch启动的job加入到了CoroutineScope所在的协程的任务队列
  • 由于协程任务没有执行完成,因此主线程不会结束,后面的all coroutine done会先打印
fun main() = runBlocking {
    val job = launch {
        delay(1000)
        println("world")
    }
    print("hello,")
    println("all coroutine done !")
}
//输出
hello,all coroutine done !
world

四个场景看协程

场景一

fun main() = runBlocking {
    var job = launch {
        println("world")
    }
    print("hello,")
}
// 输出为 
hello,world
  • launch启动了一个job,加入到CoroutineScope作用域下
  • 先执行print("hello,"),然后执行job

场景二

fun main() = runBlocking {
    var job = launch {
        delay(300)
        println("task from runblocking ")
    }

    coroutineScope {
       var jobChild = launch {
            delay(400)
            println("task from nested scope")
        }
    }
    println("end of blocking")
}
//输出
task from runblocking 
task from nested scope
end of blocking
  • launch启动了一个job,加入到CoroutineScope作用域下
  • coroutineScope它会创建一个协程作用域,并且在所有已启动子协程执行完毕之前不会结束
  • runBlocking要等所有的子作用域的执行结束才结束

场景三

fun main() = runBlocking {
    var job = launch {
        delay(300)
        println("task from runblocking ")
    }

    println("end of blocking")
}
//输出
end of blocking
task from runblocking 
  • launch启动了一个job,加入到CoroutineScope作用域下,并进入挂起状态
  • runBlocking后续代码先执行

场景四 coroutineScope方法

fun main() = runBlocking {
    var job = launch {
        delay(300)
        println("task from runblocking ")
    }

    coroutineScope {
       var jobChild = launch {
            delay(300)
            println("task from nested scope")
        }
    }
    println("end of blocking")
}
//输出
task from runblocking 
task from nested scope
end of blocking
  • launch启动了一个job,加入到CoroutineScope作用域下
  • coroutineScope它会创建一个协程作用域,并且在所有已启动子协程执行完毕之前不会结束.由于存在coroutineScope,因此后面的println("end of blocking")不会先执行.

可挂起函数 suspend/delay

  • 可挂起函数由suspend修饰,它必须在协程内部调用.
  • suspend 内部可以调用delay方法,如果没有调用delay,不需要申明为suspend
fun main() = runBlocking {
    launch {
        testDelay()
    }
}

suspend fun testDelay(){
    delay(1000)
    println("testDelay")
}

协程轻量实验

通过打印可以发现,开启了10000个协程,实际上使用的是同一个线程main.

fun main() = runBlocking {
    repeat(10000){
        launch {
            delay(1000)
            print(". ${Thread.currentThread().name}")
        }
    }
}

协程取消执行 delay/cancel

  • 当任务处于挂起状态时(有调用delay),取消才能够成功.
  • 如果没有处于挂起状态,需要增加isActive判断是否取消
  • 当协程被cancel后,会抛出JobCancellationException,它被认为是协程正常结束的原因.可以通过try/catch捕获,也可以不捕获.

非挂起函数无法取消

fun main() = runBlocking {
    var startTime = System.currentTimeMillis()
    var job =  launch {
        var i = 0
        var nextTime = startTime
        while (i < 5){
            if (System.currentTimeMillis() >= nextTime){
                println("----calc---- ${i++}")
                nextTime += 500
            }
        }
    }
    delay(1500)
    job.cancel()
    println("end")
}
//输出是
----calc---- 0
----calc---- 1
----calc---- 2
----calc---- 3
----calc---- 4
end

挂起函数可以取消

fun main() = runBlocking {
    var startTime = System.currentTimeMillis()
    var job =  launch {
        var i = 0
        var nextTime = startTime
        while (i < 5){
            delay(500)
            println("----calc---- ${i++}")
        }
    }
    delay(1500)
    job.cancel()
    println("end")
}
//输出
----calc---- 0
----calc---- 1
end

其它特殊取消

  • launch 使用Dispatchers.Default 这个context
  • 通过关键字isActive判断
fun main() = runBlocking {
    var startTime = System.currentTimeMillis()
    var job =  launch(Dispatchers.Default) {
        var i = 0
        var nextTime = startTime
        while (isActive){
            if (System.currentTimeMillis() >= nextTime){
                println("----calc---- ${i++}")
                nextTime += 1000
            }
        }
    }
    delay(1500)
    job.cancelAndJoin()
    println("end")
}