在answering a question期间,我尝试实现一种设置,其中主线程加入CommonPool
的工作以并行执行许多独立任务(这就是java.util.streams
的工作方式)。
我创建的actor数量与CommonPool
个线程一样多,外加一个用于主线程的通道。演员使用会合渠道:
val resultChannel = Channel<Double>(UNLIMITED)
val poolComputeChannels = (1..commonPool().parallelism).map {
actor<Task>(CommonPool) {
for (task in channel) {
task.execute().also { resultChannel.send(it) }
}
}
}
val mainComputeChannel = Channel<Task>()
val allComputeChannels = poolComputeChannels + mainComputeChannel
这允许我使用select
表达式为每个任务找到一个空闲的actor来分配负载:
select {
allComputeChannels.forEach { chan ->
chan.onSend(task) {}
}
}
所以我发送所有任务并关闭频道:
launch(CommonPool) {
jobs.forEach { task ->
select {
allComputeChannels.forEach { chan ->
chan.onSend(task) {}
}
}
}
allComputeChannels.forEach { it.close() }
}
现在我必须为主线程编写代码。在这里,我决定同时服务mainComputeChannel
(执行提交到主线程的任务)和resultChannel
(将各个结果累加为最终总和)
return runBlocking {
var completedCount = 0
var sum = 0.0
while (completedCount < NUM_TASKS) {
select<Unit> {
mainComputeChannel.onReceive { task ->
task.execute().also { resultChannel.send(it) }
}
resultChannel.onReceive { result ->
sum += result
completedCount++
}
}
}
resultChannel.close()
sum
}
这会导致mainComputeChannel
可能从CommonPool
线程关闭的情况,但resultChannel
仍需要服务。如果关闭了通道,则onReceive
将引发异常,而onReceiveOrNull
将立即用null
选择。两种选择都不可接受。我也没有找到避免mainComputeChannel
关闭的方法。如果我使用if (!mainComputeChannel.isClosedForReceive)
,则注册调用不会是原子的。
这引出我的问题:在某些通道可能被另一个线程关闭而其他通道仍处于活动状态的情况下,选择通道的好习惯是什么呢?
答案 0 :(得分:1)
kotlinx.coroutines
库当前缺少方便使用的原语。杰出的建议是为receiveOrClose
添加onReceiveOrClosed
函数和select
子句,使编写这样的代码成为可能。
但是,您仍然必须手动跟踪mainComputeChannel
已关闭的事实,并在关闭时停止对其进行选择。因此,使用建议的onReceiveOrClosed
子句,您将这样编写:
// outside of loop
var mainComputeChannelClosed = false
// inside loop
select<Unit> {
if (!mainComputeChannelClosed) {
mainComputeChannel.onReceiveOrClosed {
if (it.isClosed) mainComputeChannelClosed = true
else { /* do something with it */ }
}
}
// more clauses
}
有关详细信息,请参见https://github.com/Kotlin/kotlinx.coroutines/issues/330。
桌上没有提议进一步简化这种模式。