Kotlin 协程的使用

来源:互联网 发布:js防水防水涂料 编辑:程序博客网 时间:2024/06/05 14:13

Kotlin 协程


协程(Coroutine)与线程(Thread)

协程和线程的区别

协程和线程的共同目的之一是实现系统资源的上下文调用,不过它们的实现层级不同;

线程(Thraed)是比进程小一级的的运行单位,多线程实现系统资源上下文调用,是编程语言交付系统内核来进行的(可能是并发,也可能是伪并发),大部分的编程语言的多线程实现都是抢占式的,而对于这些线程的控制,编程语言无法直接控制,需要通过系统内核来进行,由系统内核决定最终的行为;

协程(Coroutine)是在语言层面实现“多线程”这个过程,一般在代码中以串行的方式表达并发逻辑,由于是在编程语言层面模拟这一过程,而非涉及到硬件层面,在编程语言层面可以完全控制这一过程,可以这么说,协程是软件层面模拟硬件层面的多线程;但不是说协程就一定是单线程,具体的实现要看具体编程语言的实现,kotlin的协程实现可能是单线程,也可能是多线程;

协程的使用场景

协程可以用于解决高负荷网络 IO、文件 IO、CPU/GPU 密集型任务等;

比如在IO线程高负载的场景下,CPU资源会被大量线程占用,这会极大浪费CPU资源,同时可能导致一些重要的线程被阻塞,代价是十分昂贵的,此时如果使用IO协程代替IO线程,可以大大减少线程数量,节省CPU资源,同时在协程挂起是几乎无代价的(不需要上下文切换或OS干预),同时编程语言对协程又有极大控制性;


Kotlin 1.1 中使用协程

Kotlin 1.1 开始提供了对于协程的支持,不过目前 kotlin 1.1 版本的协程处于实验性阶段,相关的包不包含在标准库中,需要手动导入"kotlinx-coroutines-core"包到项目依赖,可以直接 maven 中央仓库找到最新的 jar 包;
如果是使用 gradle 构建项目,在 build.gradle 脚本中需要添加类似以下:
apply plugin: 'java'apply plugin: 'kotlin'......dependencies {    compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.1'    //添加相关包依赖    ......}kotlin{             //开始协程实验性功能    experimental{        coroutines 'enable'      }}


kotlin 对于协程提供了底层的API,同时提供了高级API方便使用,这里只介绍部分高级API的基本使用;

一个基本协程例子

import kotlinx.coroutines.experimental.CommonPoolimport kotlinx.coroutines.experimental.delayimport kotlinx.coroutines.experimental.launch​fun main(args: Array<String>) {    async(CommonPool) {    //创建一个协程        delay(1000L)       //协程挂起        log("World")    }    log("Hello,")    Thread.sleep(2000L)      //主线程等待}​//输出简要日志,以下例子使用了这个函数作简要日志输出,不再重复解释fun log(message:String){ println( "[${Thread.currentThread().name}] $message") }


输出:
[main] Hello,[ForkJoinPool.commonPool-worker-1] World



协程的创建

kotlin 中可以通过 async() 函数创建一个协程,该协程创建后立即运行,一个 async 中可以包含一个 使用"suspend" 关键字声明的挂起函数,或使用一个 lambda 表达式表示的匿名方法,如下:
import kotlinx.coroutines.experimental.*;​//async 使用suspend函数创建协程fun main(args: Array<String>) {    async(CommonPool) {  //协程创建        foo()    }    log("Hello,")    Thread.sleep(2000L)}suspend fun foo(){   //挂起函数    delay(1000L)    log("World")}​//async 使用匿名函数创建协程fun main(args: Array<String>) {    async(CommonPool) {   //协程创建        delay(1000L)        log("World")    }    log("Hello,")    Thread.sleep(2000L)}


在主线程上创建协程

以上的示例都是在 CommonPool 公共线程池上创建协程的,在main主线程中创建协程,可以如下标记 main:
fun main(args: Array<String>) = runBlocking<Unit>{    async(CommonPool)  {        delay(1000L)        log("World")    }    log("Hello,")    delay(2000L) //此时main主线程也为协程,可以使用其他协程行为函数}


 

协程的行为控制

kotlin 1.1 对于协程提供了以下常用的控制方法:
dely(millis:Long):协程挂起
Deffered.join():类似Thread.join
Deffered.cancel():协程取消,可携带一个 Exception 实例作为参数,以在协程取消时抛出该异常,可以通过返回值判断协程是否取消成功
Deffered.await():等待直接获取协程返回值
/*演示join() 在调用job.join时,main协程会挂起,直到job执行结束*/fun main(args: Array<String>) = runBlocking<Unit> {    val job = async(CommonPool) {        delay(1000L)        log("World!")    }    log("Hello,")    job.join()}/*output:[main] Hello,[ForkJoinPool.commonPool-worker-1] World! */​​/*演示cancel() */fun main(args: Array<String>) = runBlocking<Unit> {    val job = async(CommonPool) {        delay(1000L)        log("World!")    }    log("Hello,")    job.cancel()}/*output:[main] Hello, */​​/*演示await() 以下示例演示了一个典型的异步过程,在执行work()之前,异步执行前置任务preWork1(),preWork2(),等待这两个 任务完全执行结束后,再执行word(); 如果使用多线程来编写,需要些大量的回调函数,协程提供了一种简洁的编写方式*/fun main(args: Array<String>) = runBlocking {    val job1 = async(CommonPool) { preWork1() }    val job2 = async(CommonPool) { preWork2() }    work(job1.await(),job2.await())}suspend fun preWork1():String{    delay(3000L)   //模拟一个耗时任务    return "job1:van ♂ yang"}suspend  fun preWork2():String{    delay(1000L)   //模拟一个耗时任务    return "job2:dark fantastic!"}fun work(str1:String,str2:String){    println("$str1\n$str2")}/*output:job1:van ♂ yangjob2:dark fantastic! */



延迟生成器

kotlin 协程还提供了类似 Python 的生成器协程函数 buildSequence,用于执行一个延迟计算队列;
一个典型的应用就是生成斐波那契数列,一般编写会用递归的方式来编写,但是假如递归层级过大,很容易造成栈溢出,同时假如需要多次不等值地调用,会造成同一个值被多次计算,比较浪费资源,buildSequence 采用协程的方式,在每次调用时进行增量计算,避免同一值的多次调用,示例如下:
fun main(args: Array<String>) {​    //斐波那契数列    val fibonacci = buildSequence {        yield(1) //返回第一个斐波那契数        var cur = 1        var next = 1        while (true) {            yield(next) // 返回下一个斐波那契数            val tmp = cur + next            cur = next            next = tmp        }    }    //取出>100的斐波那契数    for (i in fibonacci){        println(i)        if(i > 100) break    }      //取出>500的斐波那契数,此时调用时 1-100 的斐波那契数是已经被延迟队列计算好的了    for (i in fibonacci){        println(i)        if(i > 500) break    }}



协程和线程的对比示例

上面讲过协程很适合使用在高负荷IO、CPU密集型的场景,通过协程模拟并发的方式减少实际的线程数,已达到节省CPU资源的目的,以下2个示例分别创建 100,000 个线程和 100,000个协程模拟一个高负荷IO的场景:

创建 100,000 个线程
fun main(args: Array<String>) = runBlocking<Unit> {    val jobs = List(100_000) {        thread {            Thread.sleep(2000L)   //模拟IO阻塞            log("hello")        }    }    jobs.forEach(Thread::join)}


此时我的机器上创建了几千个线程后抛出栈溢出异常(-Xmx1024),同时CPU占满(i7-6700HQ)

创建 100,000 个协程
fun main(args: Array<String>) = runBlocking<Unit> {    val jobs = List(100_000) {          launch(CommonPool) {            delay(2000L)   //模拟IO阻塞            print("hello")        }    }    jobs.forEach { it.join() } //这里不能用 jobs.forEach(Job::join),因为 Job.join 是 suspend 方法}


程序运行时只创建了不到10个线程,并没有栈溢出,相比之下,在这样的场景里更适合使用协程;













原创粉丝点击