如何在golang中测试并发和锁定?

时间:2013-10-04 19:45:59

标签: testing concurrency go

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

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

正确的事件顺序应为:

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

    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 {
       ....
    }
    

1 个答案:

答案 0 :(得分:9)

基于锁的代码的并发测试很难,因为很难找到可证明正确的解决方案。通过印刷声明进行临时手动测试并不理想。

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

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

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