kotlin协程初探

介绍协程

很长一段时间我都没有写博客了,主要是这段时间忙着毕业的事,一直无法专心于做某一件事,所以很多东西都耽搁了。今天聊一聊Kotlin的协程。

协程,又称微线程,纤程。英文名Coroutine。

上面一段是我摘自阮一峰老师的博客。我们知道最近几年协程这个概念很火,不少的语言都支持了协程,如Python,go等语言。当然由于很多语言历史局限性的存在,协程的使用并不友好,最好的当属go语言的协程,只需一个简单的go关键字就可以开启协程之旅了。

今天就来说说kotlin的协程,kotlin在1.1版本后引入了协程,目前还处于实验性阶段。但是仍然可以拿来尝尝鲜,前面提到go语言的协程非常容易使用,在kotlin里面协程的使用也是非常简单的,而且还更加丰富。

使用协程

fun main(args: Array<String>) = runBlocking {
    launch {
        delay(1000L)
        println("World")
    }
}
复制代码

这段代码便可以在Kotlin中开启一个协程,与一般的主函数不同,由于协程的特殊性。Kotlin中使用协程的函数必须在前面加上suspend关键字。suspend代表着当前函数可以被挂起,从而将该函数的使用权交给调度器,调度器会在合适的时机去使用和调度该函数从而实现cpu资源的充分利用。

为了更方便的使用协程,kotlin的标准库为我们提供了runBlocking这个函数。

public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
复制代码

这个函数会默认的将{}内部的语句挂载到suspend函数中,方便我们使用协程。Kotlin中提供了两个函数启动协程,分别是launchasync,这两个函数均可以启动协程,异同点我们将在后面叙述。

launch启动协程后,延时1s,输出World。但是当真正运行这个函数时,我们其实得不到任何输出,原因很简单:

由于协程的非阻塞性,开启一段协程并不会阻塞当前的线程。协程在执行的时候,主函数仍在向下执行,可是主函数下没有任何执行语句,主函数便会退出,协程此时虽然仍在执行,但主函数已退出,整个程序就退出了。所以World这个词还没来得及输出,程序就已经退出了。

可是我们必须得知道协程是否真正的在执行,所以我们必须阻塞一下主函数,或者说让主函数不会立马退出,让协程有时间的去输出。所以这样改进我们的代码:

fun main(args: Array<String>) = runBlocking {
    val job = launch {
        delay(1000L)
        println("World")
    }
    println("Hello, ")
    Thread.sleep(2000L)
}
复制代码

如果一些顺利,我们会得到下面的输出:

Hello, 
World
复制代码

当协程开启后,主程序与协程同时执行(这里的同时并不是指真真的同时,而是CPU调度速度快使我们感觉到二者在同时执行),但协程里面正在delay,时间为1s,所以主程序先输出Hello,,然后主程序正在被Thread.sleep(2000L)所阻塞,时间为2s,这时候只被阻塞一秒的协程便有机会输出了,所以控制台后面输出World,然后程序过一会儿便会自己退出。

这里可以看出,协程是真正的非阻塞式,它不会阻塞主程序的向下执行,而且它绝对收主程序控制,主程序退出,它也会被立刻退出。当然Thread.sleep(2000L)这个函数并不地道,Kotlin提供了一个更好用的delay函数给我们,方便我们进行协程的阻塞。所以,地道的程序如下所示:

fun main(args: Array<String>) = runBlocking {
    val job = launch {
        delay(1000L)
        println("World")
    }
    println("Hello, ")
    delay(2000L)
}
复制代码

如果一切正常,你将会得到同样的输出。但是这样的程序真的就地道吗,我们指定了delay的时间,毫无疑问是不准确的,因此Kotlin为我们提供了一个更好的方法join。它会阻塞主程序,直到协程执行完毕,所以真正地道的写法是这样的:

fun main(args: Array<String>) = runBlocking {
    val job = launch {
        delay(1000L)
        println("World")
    }
    println("Hello, ")
    job.join()
}
复制代码

这样,当协程在执行的时候,主程序等待,当协程完成自己的任务后,主程序继续执行。

当然协程最最最重要的特性——轻量。操作系统的进程是非常昂贵的,线程也是价值不菲的,但是协程真的是异常便宜,比如说:

fun main(args: Array<String>) = runBlocking {
    val jobs = List(100000) {
        launch {
            delay(1000L)
            print(".")
        }
    }
    jobs.forEach { it.join() }
}
复制代码

我们开了多少了协程,没错10万个,但是计算机毫无感觉。可以想象,在高并发的情况下,协程带来了提升无疑是巨大的。

协程用于io

上面,我们很明显的感觉到了协程带来的优越性。但是更多的情况下,我们需要协程获取数据,比如说数据请求,而后得到该数据。launch开启的协程,并不能完成这一点,它只能启动协程,当然上面也提过Kotlin有两个函数可以开启协程。既然lanuch不能,那async是否可以了?答案是必须得,毕竟谁也不会没事搞两个函数。

Kotlin中的async这个函数受到了C#影响,表明当前的函数的是个异步的函数,异步的函数必然会带有自己的callback。但是为了使用方便和优美,Kotlin提供了await方法,当数据请求完毕时,协程会自动调用该方法,并且返回请求的数据。我们通过下面的例子来看一下:

fun main(args: Array<String>) = runBlocking<Unit> {
    val elasticDto = ElasticDto()  ①
    val job = async {
        elasticDto.getLastestByPage() ②
    }
    job.await().also {  ③
        println(it.toString())
    }
    elasticDto.close() ④
}
复制代码

①:创建一个数据请求的类——elasticDto。

②:用async开启一个协程,并在该函数下调用elasticDto的getLastestByPage方法请求远端的数据,请求而来的数据会自动装入job里面。

③:等待协程处理完毕,处理完毕后输出请求数据内容。

④:关闭elasticDto客户端。

lanuch不同,async会返回一个Deferred对象,该对象拥有协程完成后的回调功能,并从中取得请求来的数据。并且await会如同前面的join函数一样,阻塞当前的主程序。

当然Kotlin协程还实现了如Go语言一样的CSP模型,通过Channel来实现协程之间的通信,笔者会在下篇比较Kotlin和Go语言在该方面的异同点。Kotlin协程实现了两种模式下通信方式,很大程度上既照顾了使用callback的方式,也拥抱了CSP模型。具体请参考官方的指北

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值