Skip to content

进展保证

许多并发算法提供非阻塞进展保证,例如锁自由和等待自由。由于它们通常不平凡,很容易引入一个阻塞算法的 bug。Lincheck 可以帮助你使用模型检测策略发现活性 bug。

要检测算法的进展保证,请在 ModelCheckingOptions() 中启用 checkObstructionFreedom 选项:

kotlin
ModelCheckingOptions().checkObstructionFreedom()

创建一个 ConcurrentMapTest.kt 文件。然后添加以下测试来检测 Java 标准库中的 ConcurrentHashMap::put(key: K, value: V) 是一个阻塞操作:

kotlin
import java.util.concurrent.*
import org.jetbrains.lincheck.*
import org.jetbrains.lincheck.datastructures.*
import org.junit.*

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()。你将得到以下结果:

text
= 需具备阻塞自由性,但检测到锁 =
| ---------------------- |
|  线程 1  | 线程 2  |
| ---------------------- |
| put(1, -1) |           |
| ---------------------- |
| put(2, -2) | put(3, 2) |
| ---------------------- |

---
水平线 | ----- | 上方的所有操作发生在该线下方操作之前
---

以下交错导致错误:
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|                                         线程 1                                         |                                         线程 2                                         |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|                                                                                          | put(3, 2)                                                                                |
|                                                                                          |   put(3,2) 位于 ConcurrentHashMapTest.put(ConcurrentMapTest.kt:11)                         |
|                                                                                          |     putVal(3,2,false) 位于 ConcurrentHashMap.put(ConcurrentHashMap.java:1006)              |
|                                                                                          |       table.READ: Node[]@1 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1014)      |
|                                                                                          |       tabAt(Node[]@1,0): Node@1 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1018) |
|                                                                                          |       MONITORENTER 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1031)              |
|                                                                                          |       tabAt(Node[]@1,0): Node@1 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1032) |
|                                                                                          |       next.READ: null 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1046)           |
|                                                                                          |       switch                                                                             |
| put(2, -2)                                                                               |                                                                                          |
|   put(2,-2) 位于 ConcurrentHashMapTest.put(ConcurrentMapTest.kt:11)                        |                                                                                          |
|     putVal(2,-2,false) 位于 ConcurrentHashMap.put(ConcurrentHashMap.java:1006)             |                                                                                          |
|       table.READ: Node[]@1 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1014)      |                                                                                          |
|       tabAt(Node[]@1,0): Node@1 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1018) |                                                                                          |
|       MONITORENTER 位于 ConcurrentHashMap.putVal(ConcurrentHashMap.java:1031)              |                                                                                          |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

现在我们来为非阻塞的 ConcurrentSkipListMap<K, V> 添加一个测试,预期该测试将成功通过:

kotlin
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)
}

常见的非阻塞进展保证包括(从强到弱排列):

  • 等待自由,指无论其他线程做什么,每个操作都在有限步内完成。
  • 锁自由,保证系统级进展,即至少一个操作在有限步内完成,而特定操作可能会卡住。
  • 阻塞自由性,指如果所有其他线程都暂停,任何操作都在有限步内完成。

目前,Lincheck 仅支持阻塞自由性进展保证。然而,大多数实际的活性 bug 都会添加意外的阻塞代码,因此阻塞自由性检测也将有助于无锁和无等待算法。

下一步

了解如何显式指定测试算法的顺序规范,从而提高 Lincheck 测试的健壮性。