introduce

go run -race xxx...

The -race option is used to detect data races, and in the case of -race, the Go program will detect if a data race occurs after it runs, and it will print out the stack of errors, layer by layer, just like panic. Usually used for development. Of course, turning this option on does not necessarily detect potential data contention. It will detect when your program runs into a segment of data contention. Your program may have many modules, and when the program does not run into a segment of data contention, it will not detect it until the end of the program.

Using the -race option consumes more CPU computing resources and memory than if this option is not enabled. The actual situation is as follows: Memory: 113MB is consumed when this option is not enabled, and 550MB is consumed when this option is enabled: Don’t open this option 1 s can finish operation, opened after 15 s use is “golang.org/x/crypto/bcrypt” package bcrypt.Com pareHashAndPassword method, very resource-intensive method.

To summarize, the race option is a way of checking the security of data, both reading and writing (as opposed to both reading and writing), etc.

Demo (What is a Race anyway?)

The following demo is a common lazy singleton pattern that relies on go’s shared variables without worrying about visibility.

package main

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

var config map[string]string

func main(a) {
	count, _ := strconv.Atoi(os.Args[1])
	for x := 0; x < count; x++ {
		go getConfig()
	}
	<-time.After(time.Second)
}
func getConfig(a) map[string]string {
	if config == nil {
    fmt.Println("init config")
		config = map[string]string{}
		return config
	}
	return config
}
Copy the code

Run go run -race demo.go 100

sgcx015@172- 15- 68.- 151.: ~ /go/code/com.anthony.http % go run -race cmd/once_demo.go 100
init config // load
==================
WARNING: DATA RACE
init config //load
Write at 0x0000012109c0 by goroutine 7: // g7 on line 22
  main.getConfig()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:22 +0xd2

Previous read at 0x0000012109c0 by goroutine 8: // G8 in line 20 (race)
  main.getConfig()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:20 +0x3e

Goroutine 7 (running) created at:// These invalid messages
  main.main()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:15 +0xae

Goroutine 8 (running) created at:
  main.main()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:15 +0xae
==================
Found 1 data race(s)
exit status 66
Copy the code

It turns out that there is a read-write race, so for the way we write it, there are multiple threads loading at the same time, so it loads twice. Then our business scenario is irrelevant because it doesn’t matter how many times the configuration loads.

To sum up, a race triggers not simultaneous writes, but simultaneous reads and writes. This is a serious problem. What’s serious is that if you look at tomic.Value, on a 64-bit computer, it’s 8 bytes. There might be a case where A puts in 32 bits, and B reads 32 bits. And then A continues to write 32 bits, and the problem that happens is that the read and write is inconsistent. So atomic solved the problem.

So we also need to solve the problem is to let him load once.

Simple. Just add a lock. Then double check.

func getConfig(a) map[string]string {
	if config == nil {
		lock.Lock()
		defer lock.Unlock()
		ifconfig ! =nil {
			return config
		}
		config = map[string]string{}
		fmt.Println("init config")
		return config
	}
	return config
}
Copy the code

As a result, there are problems with competing reads and writes, of course.

sgcx015@172- 15- 68.- 151.: ~ /go/code/com.anthony.http % go run -race cmd/once_demo.go 100
init config // Load once
==================
WARNING: DATA RACE
Read at 0x0000012109c0 by goroutine 8:
  main.getConfig()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:24 +0x5b

Previous write at 0x0000012109c0 by goroutine 7:
  main.getConfig()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:30 +0xeb
==================
Found 1 data race(s)
Copy the code

So how do you solve the competition, as I mentioned above, with atomic classes.

import (
	"fmt"
	"os"
	"strconv"
	"sync/atomic"
	"time"
)

var config atomic.Value

func main(a) {
	count, _ := strconv.Atoi(os.Args[1])
	for x := 0; x < count; x++ {
		go getConfig()

	}
	<-time.After(time.Second * 2)}func getConfig(a) map[string]string {
	if config.Load() == nil {
		fmt.Println("init config")
		config.Store(map[string]string{})
		return config.Load().(map[string]string)}return config.Load().(map[string]string)}Copy the code

Execution: Discover that there really is no contest for a simple reason: atomic manipulation. And then it loads twice

~ /go/code/com.anthony.http % go run -race cmd/demo.go 1000
init config
init config
Copy the code

Atomic source code analysis

// A Value must not be copied after first use.
type Value struct {
	v interface{}}Copy the code

/ / load the source code

func (v *Value) Load(a) (x interface{}) {
  // First convert to a standard interface{} pointer (full address length)
  // v's data is an address
  // the type of v is a identifier ^uintptr(0) that indicates whether the insertion is successful
	vp := (*ifaceWords)(unsafe.Pointer(v))
  // Atomic load type address
	typ := LoadPointer(&vp.typ)
  // Uintptr (typ) == ^uintptr(0)
	if typ == nil || uintptr(typ) == ^uintptr(0) {
		// First store not yet completed. (Need to see store source code)
		return nil
	}
  // Atom-loaded value data
	data := LoadPointer(&vp.data)
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	xp.typ = typ
	xp.data = data
	return
}
Copy the code

/ / store the source code

// Store sets the value of the Value to x.
// All calls to Store for a given Value must use values of the same concrete type.
// Store of an inconsistent type panics, as does Store(nil).
func (v *Value) Store(x interface{}) {
	if x == nil {
		panic("sync/atomic: store of nil value into Value")
	}
	vp := (*ifaceWords)(unsafe.Pointer(v))
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	for {
		typ := LoadPointer(&vp.typ)
		if typ == nil {
			// Attempt to start first store.
			// Disable preemption so that other goroutines can use
			// active spin wait to wait for completion; and so that
			// GC does not see the fake type accidentally.
      // Prohibit preemption (actually gorouting internal scheduling is preemption mode) // This is not understood, it is interpreted to prevent GC reclamation
			runtime_procPin()
      // The type assignment must be successful.
			if! CompareAndSwapPointer(&vp.typ,nil, unsafe.Pointer(^uintptr(0))) {
				runtime_procUnpin()
				continue
			}
			// Complete first store.
      // Complete the substitution. So ^uintptr(0) this is just a mark to distinguish nil, belonging to a storage intermediate state
			StorePointer(&vp.data, xp.data)
			StorePointer(&vp.typ, xp.typ)
      //
			runtime_procUnpin()
			return
		}
		if uintptr(typ) == ^uintptr(0) {
			// First store in progress. Wait.
			// Since we disable preemption around the first store,
			// we can wait with active spinning.
			continue
		}
		// First store completed. Check type and overwrite data.
		iftyp ! = xp.typ {panic("sync/atomic: store of inconsistently typed value into Value")}// This can only store one type.
		StorePointer(&vp.data, xp.data)
		return}}Copy the code

// Other areas to master

// ifaceWords is interface{} internal representation.
type ifaceWords struct {
	typ  unsafe.Pointer
	data unsafe.Pointer
}
Copy the code
// LoadPointer atomically loads *addr.
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
Copy the code

conclusion

1. Race is not associated with singletons, nor can it be detected.

2. Race is a solution to the problem of simultaneous read and write.