Spec-Zone.ru › Kotlin 1.9

Гарантии прогресса

Многие алгоритмы для конкурентного программирования обеспечивают гарантии прогресса, такие как отсутствие блокировок и отсутствие ожидания. Поскольку они обычно нетривиальны, легко добавить ошибку, которая заблокирует алгоритм. Lincheck может помочь найти ошибки живучести, используя стратегию моделирования.

Чтобы проверить гарантию прогресса алгоритма, включите опцию checkObstructionFreedom в ModelCheckingOptions():

ModelCheckingOptions().checkObstructionFreedom()

Создайте файл ConcurrentMapTest.kt. Затем добавьте следующий тест, чтобы обнаружить, что ConcurrentHashMap::put(key: K, value: V) из стандартной библиотеки Java является блокирующей операцией:

import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
import org.junit.*
import java.util.concurrent.*

class ConcurrentHashMapTest {
    private val map = ConcurrentHashMap<Int, Int>()

    @Operation
    fun put(key: Int, value: Int) = map.put(key, value)

    @Test
    fun modelCheckingTest() = ModelCheckingOptions()
        .actorsBefore(1) // To init the HashMap
        .actorsPerThread(1)
        .actorsAfter(0)
        .minimizeFailedScenario(false)
        .checkObstructionFreedom()
        .check(this::class)
}

Запустите modelCheckingTest(). Вы должны получить следующий результат:

= Obstruction-freedom is required but a lock has been found =
| ---------------------- |
|  Thread 1  | Thread 2  |
| ---------------------- |
| put(1, -1) |           |
| ---------------------- |
| put(2, -2) | put(3, 2) |
| ---------------------- |

---
All operations above the horizontal line | ----- | happen before those below the line
---

The following interleaving leads to the error:
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|                                         Thread 1                                         |                                         Thread 2                                         |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|                                                                                          | put(3, 2)                                                                                |
|                                                                                          |   put(3,2) at ConcurrentHashMapTest.put(ConcurrentMapTest.kt:11)                         |
|                                                                                          |     putVal(3,2,false) at ConcurrentHashMap.put(ConcurrentHashMap.java:1006)              |
|                                                                                          |       table.READ: Node[]@1 at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1014)      |
|                                                                                          |       tabAt(Node[]@1,0): Node@1 at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1018) |
|                                                                                          |       MONITORENTER at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1031)              |
|                                                                                          |       tabAt(Node[]@1,0): Node@1 at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1032) |
|                                                                                          |       next.READ: null at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1046)           |
|                                                                                          |       switch                                                                             |
| put(2, -2)                                                                               |                                                                                          |
|   put(2,-2) at ConcurrentHashMapTest.put(ConcurrentMapTest.kt:11)                        |                                                                                          |
|     putVal(2,-2,false) at ConcurrentHashMap.put(ConcurrentHashMap.java:1006)             |                                                                                          |
|       table.READ: Node[]@1 at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1014)      |                                                                                          |
|       tabAt(Node[]@1,0): Node@1 at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1018) |                                                                                          |
|       MONITORENTER at ConcurrentHashMap.putVal(ConcurrentHashMap.java:1031)              |                                                                                          |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Теперь добавим тест для неблокирующей ConcurrentSkipListMap<K, V>, ожидая, что тест пройдет успешно:

class ConcurrentSkipListMapTest {
    private val map = ConcurrentSkipListMap<Int, Int>()

    @Operation
    fun put(key: Int, value: Int) = map.put(key, value)

    @Test
    fun modelCheckingTest() = ModelCheckingOptions()
        .checkObstructionFreedom()
        .check(this::class)
}

Общие гарантии прогресса при отсутствии блокировок (от сильнейших к наименее сильным):

  • wait-freedom, когда каждая операция завершается за ограниченное число шагов независимо от действий других потоков.

  • lock-freedom, которая гарантирует прогресс во всей системе, так что по крайней мере одна операция завершается за ограниченное число шагов, в то время как конкретная операция может заблокироваться.

  • obstruction-freedom, когда любая операция завершается за ограниченное число шагов, если все остальные потоки приостановлены.

В настоящее время Lincheck поддерживает только гарантии прогресса obstruction-freedom. Однако большинство реальных ошибок живучести добавляют неожиданный блокирующий код, поэтому проверка obstruction-freedom также поможет с lock-free и wait-free алгоритмами.

  • Получите полный код примера.

  • См. ещё один пример, где реализация очереди Майкла-Скотта тестируется на гарантии прогресса.

Следующий шаг

Узнайте, как указать последовательное описание алгоритма тестирования явно, повышая надёжность тестов Lincheck.

Последнее изменение: 25 июля 2023
Ограничения структуры данных Последовательное описание

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/progress-guarantees.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API