如何安全地跨通道选择可能同时关闭的通道?

时间:2018-07-22 09:56:02

标签: multithreading kotlin kotlin-coroutines

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),则注册调用不会是原子的。

这引出我的问题:在某些通道可能被另一个线程关闭而其他通道仍处于活动状态的情况下,选择通道的好习惯是什么呢?

1 个答案:

答案 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

桌上没有提议进一步简化这种模式。