Is studying a variable with out atomic conflicts with atomic.CompareAndSwap
? I’ve the next code:
package deal predominant
import (
"fmt"
"sync"
"sync/atomic"
)
func predominant() {
x := int32(0)
for i := 0; i < 100000; i++ {
n := int32(0)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
atomic.CompareAndSwapInt32(&n, 1, 2)
wg.Accomplished()
}()
go func() {
x += n
wg.Accomplished()
}()
wg.Wait()
}
fmt.Println(x)
}
It’s quite simple program: there are two goroutines, considered one of them name atomic.CompareAndSwap()
on n
. The opposite one reads n
(with out atomic). Operating this program with go run -race predominant.go
offers this output:
==================
WARNING: DATA RACE
Learn at 0x00c00001413c by goroutine 8:
predominant.predominant.func2()
/tmp/atomictest/predominant.go:20 +0x59
Earlier write at 0x00c00001413c by goroutine 7:
sync/atomic.CompareAndSwapInt32()
/residence/jauhararifin/.gvm/gos/go1.21/src/runtime/race_amd64.s:310 +0xb
sync/atomic.CompareAndSwapInt32()
<autogenerated>:1 +0x18
Goroutine 8 (working) created at:
predominant.predominant()
/tmp/atomictest/predominant.go:19 +0x4a
Goroutine 7 (working) created at:
predominant.predominant()
/tmp/atomictest/predominant.go:15 +0x17c
==================
A knowledge race is detected there as a result of I learn n
(with out atomic) concurrently with atomic.CompareAndSwap(&n, ..., ...)
. Discover that I set the CAS operation to all the time fail.
That is comprehensible as a result of I don’t use atomic operation when studying n
. However I learn the go sync.Mutex
implementation:
func (m *Mutex) Lock() {
// Quick path: seize unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Purchase(unsafe.Pointer(m))
}
return
}
// Sluggish path (outlined in order that the quick path will be inlined)
m.lockSlow()
}
func (m *Mutex) lockSlow() {
var waitStartTime int64
ravenous := false
awoke := false
iter := 0
previous := m.state
...
}
As you’ll be able to see, there’s a doable concurrent entry on the m.state
. Within the quick path, one goroutine can execute CAS on &m.state
. Concurrently, one other goroutine can execute previous := m.state
within the sluggish path. However having concurrent Lock()
with -race
flag doesn’t present any report of knowledge race. How is that this doable?
if race.Enabled {
race.Purchase(unsafe.Pointer(m))
}
This tells the detector that there isn’t any rivalry downside.
However the information race must also occur even when race.Purchase(unsafe.Pointer(m))
just isn’t triggered although. For instance, let’s say there are 3 goroutines:
- goroutine 1: lock the mutex, efficiently in quick path
- goroutine 2: lock the mutex in quick path, however failed and went to the sluggish path
- goroutine 2 now could be about to execute
previous := m.state
- goroutine 3: lock the mutex, it tries to execute
atomic.CompareAndSwap(...)
within the quick path - goroutine 1: launch the lock
- information race right here: goroutine 2 now execute
previous := m.state
and goroutine 3 executeatomic.CompareAndSwap(...)
concurrently. Therace.Purchase(unsafe.Pointer(m))
just isn’t known as in case of goroutine 2 and three.
In above state of affairs, each goroutine 2 and three concurrent entry m
the place considered one of them is load and the opposite is CAS, with out runtime.Purchase
being known as. But, there isn’t any information race captured.
Oh, I misunderstood your query.
I re-read the code you offered and the supply code of mutex.
The 2 examples are primarily totally different.
You might want to perceive what atomic.CompareAndSwapInt32 is doing:
- Begin atomic execution
- Learn state variable, evaluate previous variable, and write new when there may be consistency
- Finish atomic
The one goroutine that may execute the above is the primary goroutine (A) that grabs the lock.
So what are the next goroutines doing?
- Begin atomic execution
- Learn state variable, evaluate previous variable, discover inconsistency, don’t write new
- Finish atomic
- Learn state variable (previous := m.state)
- …
Did you discover it? The following goroutines solely contain learn operations, and no write happens. Concurrent studying of variables is not going to generate race.
An instance of simplification is:
sort T struct {
x int32
}
func (t *T) X() {
if atomic.CompareAndSwapInt32(&t.x, 0, 1) {
return
}
_ = t.x
//fmt.Println(t.x)
}
Did you discover it? The following goroutines solely contain learn operations, and no write happens. Concurrent studying of variables is not going to generate race.
In the event you verify my unique code:
n := int32(0)
go func() {
atomic.CompareAndSwapInt32(&n, 1, 2)
}()
go func() {
x += n
}()
^ the atomic.CompareAndSwapInt32
above is rarely succesfully swap the n
. As a result of the n
is initiated with 0
, and the CAS swap it from 1
to 2
. So the CAS is all the time failed. It solely reads n
, and do nothing. It by no means swap the n
. In the long run of this execution, n
will all the time be assured to be zero.
So, in my unique code, there isn’t any write happens as nicely. However, Go depend it as information race.
And should you see my instance above:
- goroutine 1: lock the mutex, efficiently in quick path
- goroutine 2: lock the mutex in quick path, however failed and went to the sluggish path
- goroutine 2 now could be about to execute
previous := m.state
- goroutine 3: lock the mutex, it tries to execute
atomic.CompareAndSwap(...)
within the quick path - goroutine 1: launch the lock
- information race right here: goroutine 2 now execute
previous := m.state
and goroutine 3 executeatomic.CompareAndSwap(...)
concurrently. Therace.Purchase(unsafe.Pointer(m))
just isn’t known as in case of goroutine 2 and three.
Within the step (6) above, each goroutine 2 and three can entry the identical information m.state
, the place goroutine 3 would write it via CAS, and goroutine 2 will erad it with out atomic. And this doesn’t set off information race detection.
emmm, you might be proper.
That is very fascinating. I modified the usual library and added a perform:
func(m *Mutex)Take a look at(){
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Purchase(unsafe.Pointer(m))
}
return
}
_=m.state
}
It’s discovered that no warning is generated when calling? Evidently the usual library has some ignoring mechanism?
That is my first speculation. However I don’t know the right way to proof this.
I checked some supplies:
and a few supply code:
src/runtime/race/*
src/runtime/race.go
src/sync/*
Evidently race detection is carried out by exterior devices, however I nonetheless don’t know the right way to register particular ignore packages.
Evidently I’ve to investigate the workflow of goroutine…
However I don’t have the vitality to proceed exploring for the time being.
However I’ll look forward to different folks’s solutions.
1 Like