8

我们正在尝试测试锁。基本上,有多个客户端试图获得对特定密钥的锁定。在下面的示例中,我们使用了键“x”。

我不知道如何测试锁定是否有效。我只能阅读日志以确定它是否正常工作。

正确的事件顺序应该是:

  1. client1 获得对密钥“x”的锁定
  2. client2 尝试获取密钥“x”的锁定 (fmt.Println("2 getting lock")) - 但被阻止并等待
  3. client1 释放对键“x”的锁定
  4. client2 获得对密钥“x”的锁定

Q1:我怎样才能自动化这个过程并将其变成一个测试?

Q2:一般测试并发/互斥锁有哪些技巧?

func TestLockUnlock(t *testing.T) {
    client1, err := NewClient()
    if err != nil {
        t.Error("Unexpected new client error: ", err)
    }

    fmt.Println("1 getting lock")
    id1, err := client1.Lock("x", 10*time.Second)
    if err != nil {
        t.Error("Unexpected lock error: ", err)
    }
    fmt.Println("1 got lock")

    go func() {
        client2, err := NewClient()
        if err != nil {
            t.Error("Unexpected new client error: ", err)
        }
        fmt.Println("2 getting lock")
        id2, err := client2.Lock("x", 10*time.Second)
        if err != nil {
            t.Error("Unexpected lock error: ", err)
        }
        fmt.Println("2 got lock")

        fmt.Println("2 releasing lock")
        err = client2.Unlock("x", id2)
        if err != nil {
            t.Error("Unexpected Unlock error: ", err)
        }
        fmt.Println("2 released lock")
        err = client2.Close()
        if err != nil {
            t.Error("Unexpected connection close error: ", err)
        }
    }()

    fmt.Println("sleeping")
    time.Sleep(2 * time.Second)
    fmt.Println("finished sleeping")

    fmt.Println("1 releasing lock")
    err = client1.Unlock("x", id1)
    if err != nil {
        t.Error("Unexpected Unlock error: ", err)
    }

    fmt.Println("1 released lock")

    err = client1.Close()
    if err != nil {
        t.Error("Unexpected connection close error: ", err)
    }

    time.Sleep(5 * time.Second)
}

func NewClient() *Client {
   ....
}

func (c *Client) Lock(lockKey string, timeout time.Duration) (lockId int64, err error){
   ....
}

func (c *Client) Unlock(lockKey string) err error {
   ....
}
4

1 回答 1

18

基于锁的代码的并发测试很困难,以至于难以获得可证明的正确解决方案。通过打印语句进行临时手动测试并不理想。

有四个动态并发问题本质上是不可测试的(更多)。除了性能测试之外,统计方法是您可以通过测试代码实现的最佳方法(例如,确定 90% 的性能优于 10ms 或死锁的可能性小于 1%)。

这是 Go 提供的通信顺序进程 (CSP) 方法比共享内存上的锁更好用的原因之一。考虑你的被测 Goroutine 提供了一个具有指定行为的单元。这可以针对其他通过通道提供必要测试输入并通过通道监控结果输出的 Goroutine 进行测试。

使用 CSP,使用没有任何共享内存的 Goroutines(并且没有通过指针无意中共享内存)将保证在任何数据访问中都不会发生竞争条件。使用某些经过验证的设计模式(例如Welch、Justo 和 WIllcock)可以确定 Goroutine 之间不会出现死锁。然后仍然需要确定功能行为是否正确,上面提到的 Goroutine 测试工具可以很好地做到这一点。

于 2013-10-05T10:38:19.647 回答