Monday, April 21, 2025
HomeGolangDoes Non Atomic Load Conflicts With CompareAndSwap? - Getting Assist

Does Non Atomic Load Conflicts With CompareAndSwap? – Getting Assist


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:

  1. goroutine 1: lock the mutex, efficiently in quick path
  2. goroutine 2: lock the mutex in quick path, however failed and went to the sluggish path
  3. goroutine 2 now could be about to execute previous := m.state
  4. goroutine 3: lock the mutex, it tries to execute atomic.CompareAndSwap(...) within the quick path
  5. goroutine 1: launch the lock
  6. information race right here: goroutine 2 now execute previous := m.state and goroutine 3 execute atomic.CompareAndSwap(...) concurrently. The race.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:

  1. Begin atomic execution
  2. Learn state variable, evaluate previous variable, and write new when there may be consistency
  3. 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?

  1. Begin atomic execution
  2. Learn state variable, evaluate previous variable, discover inconsistency, don’t write new
  3. Finish atomic
  4. 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:

  1. goroutine 1: lock the mutex, efficiently in quick path
  2. goroutine 2: lock the mutex in quick path, however failed and went to the sluggish path
  3. goroutine 2 now could be about to execute previous := m.state
  4. goroutine 3: lock the mutex, it tries to execute atomic.CompareAndSwap(...) within the quick path
  5. goroutine 1: launch the lock
  6. information race right here: goroutine 2 now execute previous := m.state and goroutine 3 execute atomic.CompareAndSwap(...) concurrently. The race.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

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments