并行执行协程
假如我们有两个挂起函数:
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // pretend we are doing something useful here, too
return 29
}
当我们想串行的执行它们时,直接按顺序调用即可:
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
当我们想并行执行时,应该如何做呢?使用 async 方法即可:
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
async 与 launch 差不多,不过,launch 返回的是一个 Job,并不携带任何结果值,而 async 返回的是一个延迟(Deferred),一个轻量级的非阻塞Future,代表着稍后提供结果。
协程延迟启动
当我们使用 async 方式启动协程的时候,还可以给协程指定一下启动模式:
@JvmStatic
fun main(args: Array<String>) = runBlocking {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// some computation
delay(1000)
one.start() // start the first one
two.start() // start the second one
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
设置启动模式为 LAZY 后,只有当我们主动调用了 start 或者 await 方法时,协程才会开始执行。
输出结果:
The answer is 42
Completed in 2011 ms
可以看到,在 delay 1000 之前,协程并没有开始执行。
Channel
一般我们使用协程,都会让协程运行在子线程上,所以当协程多起来的时候,协程之间的合作与通信就是一个蛋疼的问题了。好在有了 Channel,它就类似于一个 BlockingQueue,用来处理生产者与消费者之间的同步问题。
当多个协程从同一channel接收信息时,每个元素只由其中一个消费者处理一次。一旦处理完一个元素,就会立即从channel中删除。
Channel 有 3 种:
interface SendChannel<in E> {
suspend fun send(element: E)
fun close(): Boolean
}
interface ReceiveChannel<out E> {
suspend fun receive(): E
}
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
SendChannel 就是发送数据的。
ReceiveChannel 就是接收数据的。
Channel,这个玩意继承了上面两个,想来是一个组合体,一个对象可以实现通信。
Channel又分4种:
-
Unlimited channel,就是里面可以放无限多的数据
-
Buffered channel,有限的数据
-
Rendezvous channel,0个数据,发送会阻塞,除非有接收。接收会阻塞,除非有发送。
-
Conflated channel,新的会覆盖旧的,size = 1.
看一个通信的例子:
@JvmStatic
fun main(array: Array<String>) = runBlocking<Unit> {
val channel = Channel<String>(Channel.CONFLATED)
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
launch {
channel.send("C1")
log("C done")
}
launch {
repeat(3) {
val x = channel.receive()
log(x)
}
}
}
最终输出结果:
[main] A done
[main] B done
[main] C done
[main] C1