在 Kotlin 中锁定互斥锁的正确方法

Correct way of locking a mutex in Kotlin

我想使用 Kotlin Coroutines 实现一个简单的线程安全 Buffer,因为项目中已经使用了协程。

缓冲区将在多线程和单线程上下文中使用,因此 suspend fun getMostRecentData() 似乎不太合理(请参见下面的代码)。

这就是我目前所拥有的。我必须编写所有代码来锁定互斥量这一事实让我想知道我是否做错了什么。

无论如何,这是代码:

class SafeBuffer(
    private val dispatcher: CoroutineDispatcher,
    private val bufferSize: Int
    ) {

    private val buffer = LinkedList<MyDataType>()
    private val mutex = Mutex()

    val size: Int
        get() = buffer.size

   // First approach: make a suspend fun
   // Not great because I will need a runBlocking{} statement somewhere, every time I want to access the buffer
   suspend fun getMostRecentData() : MyDataType? {
        mutex.withLock {
            return if (buffer.isEmpty()) null else buffer.last
        }
    }

   // Second approach: use a runBlocking block inside the function
   // Seems like it is missing the purpose of coroutines, and I'm not 
   // sure it is actually thread safe if other context is used somehow?
   fun getMostRecentData() : MyDataType? {
        runBlocking(dispatcher) {
            mutex.withLock {
                return if (buffer.isEmpty()) null else buffer.last
            }
        }
    }

    /**** More code ****/
    (...)

}

那么最idiomatic/elegant实现这一目标的方法是什么?

扩展我的评论,我认为让缓冲区 class 只公开一个 suspend fun 是惯用的,因为 class 的消费者将负责弄清楚他们想如何使用它(通过 runBlocking 或从另一个协程)。如果您看到这个用例经常出现,惯用的方法可能是在 SafeBuffer 上有一个扩展函数来提供这个功能。

扩展函数在协程中随处可见API。在您的代码示例中,甚至 Mutex.withLock 也被定义为扩展函数。

class SafeBuffer(...) {

    private val buffer = LinkedList<MyDataType>()
    private val mutex = Mutex()

    suspend fun getMostRecentData() : MyDataType? =
        mutex.withLock {
            if (buffer.isEmpty()) null else buffer.last
        }
}

fun SafeBuffer.getMostRecentDataBlocking(): MyDataType? =
    runBlocking {
        getMostRecentData()
    }