sourcegraph / conc

Better structured concurrency for go
https://about.sourcegraph.com/blog/building-conc-better-structured-concurrency-for-go
MIT License
9.38k stars 322 forks source link

BUG: Use in loop conc.WaitGroup output is incorrect #146

Closed xpfo-go closed 1 week ago

xpfo-go commented 1 week ago

func TestTh(t *testing.T) {
    mu := sync.Mutex
    countMap := make(map[int]int)
    var wg conc.WaitGroup
    for i := 0; i < 10; i++ {
        countF := func(id int) {
            mu. Lock()
            countMap[id] = id * id
            mu.Unlock()
        }
        wg.Go(func() {
            countF(i)
        })
    }
    if f:= wg.WaitAndRecover(); f != nil {
        t. Log(f.String())
        return
    }
    t. Log(countMap)
}

countMap the output is incorrect

varungandhi-src commented 1 week ago

This is not a bug in conc, the problem is that you have a data race on i. Specifically, the func passed to wg.Go is capturing i by reference and reading from it, and the end of the loop iteration is writing to i with i++. These two operations are concurrent, there is no synchronization for access to i, both operations are not reads, and the operations are not atomic, so this is a data race.

A data race is defined as a write to a memory location happening concurrently with another read or write to that same location, unless all the accesses involved are atomic data accesses as provided by the sync/atomic package

-- https://go.dev/ref/mem

If you do an extra j := i and pass j to wg.Go, then the output will no longer be buggy.

xpfo-go commented 1 week ago

This is not a bug in conc, the problem is that you have a data race on i. Specifically, the func passed to wg.Go is capturing i by reference and reading from it, and the end of the loop iteration is writing to i with i++. These two operations are concurrent, there is no synchronization for access to i, both operations are not reads, and the operations are not atomic, so this is a data race.这不是 conc 中的错误,问题是 i 上存在数据争用。具体来说,传递给的 func wg.Go 是通过引用捕获 i 并从中读取的,而循环迭代的结尾是写入 to i i++ 。这两个操作是并发的,没有同步的访问, i 两个操作都不是读取的,而且操作也不是原子的,所以这就是数据竞争。

A data race is defined as a write to a memory location happening concurrently with another read or write to that same location, unless all the accesses involved are atomic data accesses as provided by the sync/atomic package -- https://go.dev/ref/mem

If you do an extra j := i and pass j to wg.Go, then the output will no longer be buggy.如果您执行 extra j := i 并传递给 j wg.Go ,则输出将不再有问题。

I am very sorry, thank you for your answer。