go - 想要数据竞赛还是糟糕的设计?

标签 go data-race

我正在实现一个应用程序,该应用程序集成了每秒点击次数限制的第三方 API。我写了我的适配器,在我用竞争条件检测器运行我的测试之前,我是一个快乐的人。

设计很简单,有一个:

  • 计算它发出的请求的结构
  • 每秒将此计数器重置为 0 的滴答声
  • 此结构上的私有(private)函数在满足条件之前一直阻塞,以允许对 API 进行额外调用。

  • 运行这个测试用例效果很好,直到你给它 -race旗帜。
    我相信数据竞争是由试图重置命中计数器的滴答线程和增加它的调用请求引起的......

    我的设计是坏的还是我应该忍受数据竞争警报?
    
    import (
        "sync"
        "testing"
        "time"
    )
    
    var subject httpClientWrapper
    
    func init() {
        subject = httpClientWrapper{
            hits:       0,
            hitsSecond: 1,
        }
        // reset hits every second to 0
        go func() {
            tick := time.Tick(1 * time.Second)
            for range tick {
                subject.hits = 0
            }
        }()
    }
    
    type httpClientWrapper struct {
        hits, hitsSecond int
    }
    
    var m sync.Mutex
    
    func (c *httpClientWrapper) allowCall() {
        m.Lock()
        callAllowanceReached := c.hits >= c.hitsSecond
        for callAllowanceReached {
            // cool down for one second
            time.Sleep(1 * time.Second)
            callAllowanceReached = c.hits >= c.hitsSecond
        }
        c.hits = c.hits + 1
        m.Unlock()
    }
    
    func TestItSleeps(t *testing.T) {
        timeStart := time.Now()
        var wg = sync.WaitGroup{}
        for i := 0; i < 3; i++ {
            wg.Add(1)
            go func() {
                subject.allowCall()
                wg.Done()
            }()
        }
        wg.Wait()
        elapsedTime := time.Since(timeStart)
        if elapsedTime < (1 * time.Second) {
            t.Errorf("this test should not had been able to run in less than a second due to locks and cool down")
        }
    }
    

    最佳答案

    任何访问 .hits应该在互斥体后面,所以

    // reset hits every second to 0
    go func() {
        tick := time.Tick(1 * time.Second)
        for range tick {
            m.Lock()
            subject.hits = 0
            m.Unlock()
        }
    }()
    

    此外,任何 sleep 都不应该在互斥锁锁定的情况下发生,所以
    m.Lock()
    ...
        {
            m.Unlock()
            // cool down for one second
            time.Sleep(1 * time.Second)
            m.Lock()
            ...
        }
    ...
    m.Unlock()
    

    关于go - 想要数据竞赛还是糟糕的设计?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59525738/

    相关文章:

    go - 当值是 reflect.Ptr 时如何使用 reflect.FieldByName

    json - 执行自定义解码后解码剩余的 JSON

    go - 如何使用 math/big 对 bigInt 求模?

    c++ - 尝试同步队列时发生段错误

    r - 为什么Sys.setenv()在R中没有立即生效?

    go - 为什么此代码会导致数据争用?

    javascript - 如何让 JavaScript 回调等待另一个回调?

    go - 在 Golang 中返回一个带有接收者的函数

    postgresql - 如何避免 0(零)int 变成 Postgres "null"值并违反 "not null"约束?