Привет. Меня зовут Оиши, и я разрабатываю мобильный клиент для приложения Communication “.
Эта статья является 71 -й частью серии, разделяемой «Отчет о комитете по обзору». Line Yahoo имеет деятельность под названием Комитет по обзору для поддержания высокой производительности развития. Мы регулярно делимся знаниями, которые мы собираем здесь с нашей компанией, и некоторые из них также публикуются в этом блоге. (Для получения дополнительной информации о отчете комитета по рассмотрению см.Список прошлых статей(Пожалуйста, обратитесь к)
Остерегайтесь конфликтов мутекс
Предположим, вы хотите написать код, который обрабатывает нерегулярные события, которые происходят в соответствии с правилами ниже:
- После того, как событие было обработано, необходимо подождать определенный интервал, прежде чем обрабатывать следующее событие.
- Если в течение этого интервала происходит несколько событий, они обрабатываются одновременно.
Это поведение иллюстрируется следующим образом:
Это поведение естьДебютировать и дроссельЭто похоже на операцию, но не удаляет события в середине.
Я реализовал код для достижения этого следующего:
class EventProcessor(
// Injectable for unit testing.
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) {
private val queuingMutex: Mutex = Mutex()
private val consumingMutex: Mutex = Mutex()
@GuardedBy("queuingMutex")
private val eventQueue: MutableList = mutableListOf()
fun postEvent(event: Event) {
coroutineScope.launch {
enqueue(event)
consumeEvents()
}
}
private suspend fun enqueue(event: Event): Unit = queuingMutex.withLock {
eventQueue.add(event)
}
private suspend fun consumeEvents() {
if (!consumingMutex.tryLock()) {
// この場合、別のコルーチンが `consumeEvents()` を実行中だとわかるのでスキップする。
return
}
try {
while (true) {
val events = dequeueAll()
if (events.isEmpty()) {
break
}
processEvents(events)
delay(THROTTLING_DELAY_MILLIS)
}
} finally {
consumingMutex.unlock()
}
}
private suspend fun dequeueAll(): List = queuingMutex.withLock {
val events = eventQueue.toList()
eventQueue.clear()
events
}
private suspend fun processEvents(events: List) {
TODO()
}
companion object {
private const val THROTTLING_DELAY_MILLIS: Long = 1000
}
}
Вы понимаете проблему с этим кодом?
Подводные камни Трика
Приведенный выше код имеет проблему с условием гонки. Это следующий сценарий:
| Coroutine a | Coroutine б |
|---|---|
enqueue(eventA) |
|
consumingMutex.tryLock() но true Возвращаться |
|
dequeueAll() но [eventA] Возвращаться |
|
processEvents([eventA]) |
|
delay(THROTTLING_DELAY_MILLIS) |
|
dequeueAll() но [] Возвращаться |
|
enqueue(eventB) |
|
consumingMutex.tryLock() но false Возвращаться |
|
consumingMutex.unlock() |
|
(eventB Остается в очереди, и как A, так и B конец.) |
Это”tryLock() Это проблема, вызванная логикой, что, если код не удается, он считает, что другая скручивание работает и пропускает свою собственную обработку.
Следовательно, это состояние раса tryLock() Без использования withLock { ... } Вы можете исправить это, используя это нормально. Кроме того, при этом consumeEvents() Внутри while (true) Там тоже нет необходимости в петлях.
Наконец, после коррекции consumeEvents() Это следующее:
private suspend fun consumeEvents(): Unit = consumingMutex.withLock {
val events = dequeueAll()
if (events.isNotEmpty()) {
processEvents(events)
delay(THROTTLING_DELAY_MILLIS)
// もしここの時点で eventQueue が空でないのなら、キューにイベントを追加した別のコルーチンが居ることを意味し、
// そのコルーチンが次にこの consumeEvents() の処理を実行することが保証されている。
// よって、現在のコルーチンはここで単に終了してしまってよい。
}
}
Правильное использование TryLock
tryLock() Следует заботиться при использовании. Это относится не только к котлину, но и на любой язык программирования.
Например, язык GO TryLock Следующие предупреждения включены:
https://pkg.go.dev/sync#mutex.trylock
(Перевод), безусловно, существует истинное правильное использование TryLock, но это редко, и использование TryLock обычно является признаком того, что существует фундаментальная проблема с тем, как Mutex используется.
tryLock() обычно используется, чтобы избежать тупиков при блокировке нескольких мутекс. То есть несколько мутекс tryLock() Если вы попытаетесь потерпеть неудачу хотя бы один, все unlock() Метод, который включает в себя повторение с самого начала после соответствующей случайной задержки,tryLock() Это типичный вариант использования.
Таким образом,tryLock() Если это терпит неудачу
2025-08-07 02:00:00
1754593435
#Методы #улучшения #качества #кода #Остерегайтесь #соревнований #Mutex
Ещё по этой теме