想要数据竞争还是糟糕的设计?
Wanted data-race or bad design?
我正在实施一个集成第三方 API 的应用程序,该应用程序具有每秒点击次数限制。我写了我的适配器,我是一个快乐的人,直到我 运行 我用竞争条件检测器进行测试。
设计简单,有一个:
- 一个计算它发出的请求的结构
- 每秒将此计数器重置为 0 的滴答声
- 此结构上的私有函数,在满足条件以允许对 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()
}
}()
此外,互斥体锁定时不应发生任何休眠,因此
m.Lock()
...
{
m.Unlock()
// cool down for one second
time.Sleep(1 * time.Second)
m.Lock()
...
}
...
m.Unlock()
我正在实施一个集成第三方 API 的应用程序,该应用程序具有每秒点击次数限制。我写了我的适配器,我是一个快乐的人,直到我 运行 我用竞争条件检测器进行测试。
设计简单,有一个:
- 一个计算它发出的请求的结构
- 每秒将此计数器重置为 0 的滴答声
- 此结构上的私有函数,在满足条件以允许对 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()
}
}()
此外,互斥体锁定时不应发生任何休眠,因此
m.Lock()
...
{
m.Unlock()
// cool down for one second
time.Sleep(1 * time.Second)
m.Lock()
...
}
...
m.Unlock()