I am working with Milind to fix the data race in Uber's Go database. Here is the common data races concluded from his paper: "A Study of Real-World Data Races in Golang".
Pattern 1: Loop variable capture by reference.
In Go, the loop iterator variable in for range
is a single variable that takes different values in each loop iteration. (Loop variable capture by reference.)
It's better to make a local copy when using goroutines to process the loop variables.
1 2 3 4 5 6 7 |
for val := range container { + val := val go func() { // processing val }() } |
Pattern 2: Error variable capture
Mixed use of error variable. In the following example, the concurrent writes on the return error variable from foo2()
and foo3()
cause a data race.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
_, err := foo1() if err != nil { // do something } go func() { - _, err = foo2() + _, err := foo2() if err != nil { // do something } } _, err = foo3() if err != nil { // do something } |
Pattern 3: Normal return in a function with a named return
In Go, there is a feature called "named result parameters". The return variable can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a return
statement without arguments, the current values of the result parameters are used as the returned values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func namedReturnCallee(x int) (res int) { res = 10 if x > 0 { return // "return 10" } + newRes := res go func() { - _ = res // read res + _ = newRes }() return 20 // "res = 20" } |
Pattern 4: Deferred functions in a named return
Go offers a keyword defer
to schedule a function call (the deferred function) to be run immediately before the function executing the defer
returns.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func namedReturnWithDefer() (res string, err error) { defer func() { res, err = foo1() }() _, err = foo2() // do something + newErr := err go func() { - process_err(err) + process_err(newErr) }() return // defer function runs immediately before here } |
Pattern 5: Meta fields of Slice change
The append
operation appends the elements to the slice's end and returns the result. The slice may change as the number of elements reaches the slice's length, which returns a new slice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
uuids := []string{"a", "b", "c", "d"} var res []string var mutex sync.Mutex safeAppend := func(str string) { mutex.Lock() res = append(res, str) mutex.Unlock() } for _, uuid := range uuids { uuid := uuid go func(r []string) { safeAppend(uuid) }(res) // slice read without holding lock } |
Pattern 6: Data Races on Thread-Unsafe Map
In Go, a map (hash table) is a sparse data structure, and accessing one element might result in accessing another element; if during the same process another insertion/deletion happens, it will modify the sparse data structure and cause a data race.
1 2 3 4 5 6 7 8 9 10 |
uuids := []string{"a", "b", "c", "d", "e"} var errMap = make(map[string]error) for _, uuid := range uuids { uuid := uuid go func() { // errMap keeps inserting elements errMap[uuid] = errors.New(uuid) }() } |
Pattern 7: Incorrect use of synchronization constructs
Incorrect use of synchronization constructs such as sync.Mutex
and sync.RWMutex
, which are value types (structures) in Go, often causes data races.
In the following example, the mutex passed to the two goroutines is captured by value. The two goroutines possess different mutex objects sharing no internal state. A correct implementation should pass the address of mutex (&mutex
).
1 2 3 4 5 6 7 8 9 10 11 |
mutex := sync.Mutex{} safeUpdate := func(m sync.Mutex) { m.Lock() globalVal++ m.Unlock() } go safeUpdate(mutex) go safeUpdate(mutex) |
Pattern 8: Mixing Shared Memory with Message Passing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func (f *responseFutureImpl) Start() { go func() { resp, err := f.f() f.response = resp f.err = err time.Sleep(time.Second * 15) f.ch <- 1 // may be dead lock }() } // Waits for future func (f *responseFutureImpl) Wait(ctx context.Context) error { select { case <-f.ch: return nil case <-ctx.Done(): f.err = errCancelled // data race with read in GetError return errCancelled } } |
Pattern 9: Incorrect Use of Flexible Group Synchronization
Go offers more leeway in its group synchronization construct sync.WaitGroup
. The number of participants is dynamic. Incorrect placement of Add
and Done
methods of a sync.WaitGroup
leads to data races.
In the following example, the incorrect placement of wg.Add(1)
fails to guarantee the racyAccess
is accessed before reaching wg.Wait()
.
1 2 3 4 5 6 7 8 9 10 11 |
var wg sync.WaitGroup var racyAccess int + wg.Add(1) go func() { - wg.Add(1) racyAccess++ wg.Done() }() wg.Wait() racyAccess++ |
Also, there is an example of incorrect placement of wg.Done()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func work(wg *sync.WaitGroup) int { - defer wg.Done() return 42 } func main() { var wg sync.WaitGroup wg.Add(1) go func() { racyAccess = work(&wg) + wg.Done() }() wg.Wait() racyAccess++ } |
Pattern 10: Mutating Shared Data in a Reader-Lock-Protected Critical Section
Go offers a reader-writer lock RWMutex
, which allows concurrent readers to execute a critical section simultaneously. The RLock/RUnlock
methods on an RWMutex
hold the lock in a read-only mode. Sometimes developers accidentally put statements that may modify shared data in critical sections protected by RWMutex
, while using RLock/RUnlock
methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
type Test struct { mutex sync.RWMutex ready bool } func cond() bool { return true } func (t *Test) updateTest() { t.mutex.RLock() defer t.mutex.RUnlock() _ = t.ready if cond() { t.ready = true } } func main() { var t Test var wg sync.WaitGroup wg.Add(2) go func() { t.updateTest() wg.Done() }() go func() { t.updateTest() wg.Done() }() wg.Wait() } |
Reference:
Chabbi, Milind, and Murali Krishna Ramanathan. "A study of real-world data races in Golang." Proceedings of the 43rd ACM SIGPLAN International Conference on Programming Language Design and Implementation. 2022.
最新评论
感谢博主,让我PyTorch入了门!
博主你好,今晚我们下馆子不?
博主,你的博客用的哪家的服务器。
您好,请问您对QNN-MO-PYNQ这个项目有研究吗?想请问如何去训练自己的数据集从而实现新的目标检测呢?
where is the source code ? bomb1 188 2 8 0 0 hello world 0 0 0 0 0 0 1 1 9?5
在安装qemu的过程中,一定在make install 前加入 sudo赋予权限。
所以作者你是训练的tiny-yolov3还是yolov3...
很有用