Go -race是啥? atomic解決了啥

介紹

go run -race xxx...golang

-race選項用於檢測數據競爭,在使用了-race的狀況下,go程序跑起來以後,若是發生數據競爭,它就能檢測到,它就會一層一層地把錯誤棧打印出來,就像打印panic同樣。一般用於開發。固然開啓了該選項也未必能檢測出潛在的數據競爭,當你的程序跑到數據競爭的片斷它就會檢測出來,你的程序可能有不少模塊,當程序沒有執行到數據競爭的地方那直到整個程序執行結束它也檢測不出來。安全

使用-race選項相比不開啓此選項會消耗更多的cpu計算資源和內存,實際上的狀況是: 內存方面:不開啓此選項時消耗113MB內存,開啓以後550MB cpu方面:不開啓此選項1s能夠完成的操做,開啓以後15s 用的是"golang.org/x/crypto/bcrypt"包的bcrypt.CompareHashAndPassword方法,很是消耗資源的方法。ide

總結一下,其實就是race選項其實就是檢測數據的安全性的,同時讀寫(而不是同時讀,同時寫),等狀況。源碼分析

demo (race 究竟是什麼)

下面這個demo就是一個常見的 懶漢式單例模式,依靠go的共享變量不須要擔憂可見性的問題。ui

package main

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

var config map[string]string

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

執行go run -race demo.go 100atom

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在22行寫
  main.getConfig()
      /Users/sgcx015/go/code/com.anthony.http/cmd/once_demo.go:22 +0xd2

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

Goroutine 7 (running) created at:// 這些無效信息
  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
複製代碼

發現出現讀寫競爭了,那麼對於咱們這種寫法來講,確實存在多個線程同時去load,因此加載了兩次。那麼咱們的業務場景是可有可無的,由於配置加載幾回無所謂。spa

​ 這裏總結一下,race觸發的條件不是 同時寫,而是讀寫同時發生,這個問題很嚴重,嚴重在哪呢,其實看一下tomic.Value就知道了,計算機64位的,8個字節,對於32位的機器,回去讀兩次。可能會出現一種狀況是 a入32位字節,此時b讀取了32位。而後a繼續寫入32位,此時發生的問題,就是讀寫不一致。因此atomic解決了這個問題。線程

那麼我們也須要解決問題是讓他加載一次。指針

簡單點,就是加個鎖。而後雙重檢測一下。code

func getConfig() map[string]string {
	if config == nil {
		lock.Lock()
		defer lock.Unlock()
		if config != nil {
			return config
		}
		config = map[string]string{}
		fmt.Println("init config")
		return config
	}
	return config
}
複製代碼

執行結果仍是出現了競爭讀寫的問題,必然的。

sgcx015@172-15-68-151:~/go/code/com.anthony.http % go run -race cmd/once_demo.go 100
init config //加載一次
==================
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)
複製代碼

如何解決競爭呢,上面都說了,用atomic類。

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

var config atomic.Value

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

	}
	<-time.After(time.Second * 2)
}
func getConfig() 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)
}
複製代碼

執行: 發現確實沒有競爭,緣由很簡單,就是atomic原子操做。而後load了兩次

~/go/code/com.anthony.http % go run -race cmd/demo.go 1000
init config
init config
複製代碼

atomic源碼分析

// A Value must not be copied after first use.(copy ,而不是指針傳遞,後面能夠看一下實現)
type Value struct {
	v interface{}
}
複製代碼

// load源碼

func (v *Value) Load() (x interface{}) {
  // 首先轉換成了一個標準的interface{}指針(完整的地址長度)
  // v 的data是一個地址
  // v 的type是一個標誌符^uintptr(0),表示是否插入成功
	vp := (*ifaceWords)(unsafe.Pointer(v))
  // 原子加載類型地址
	typ := LoadPointer(&vp.typ)
  // 空是沒有存, uintptr(typ) == ^uintptr(0)是表示沒有存儲完成(中間態,其實我感受就是個樂觀鎖)
	if typ == nil || uintptr(typ) == ^uintptr(0) {
		// First store not yet completed.// 第一次加載尚未完成。(須要看store的源碼)
		return nil
	}
  // 原子加載值的數據
	data := LoadPointer(&vp.data)
	xp := (*ifaceWords)(unsafe.Pointer(&x))
	xp.typ = typ
	xp.data = data
	return
}
複製代碼

// store源碼

// 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.
      // 禁止搶佔(其實gorouting的內部調度是搶佔模式)//這個不理解,它的解釋是爲了防止GC回收掉
			runtime_procPin()
      // atomic賦值,失敗繼續賦值(也就是type肯定了必定成功賦值了)
			if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
				runtime_procUnpin()
				continue
			}
			// Complete first store.
      // 完成替換。因此^uintptr(0)這個就是一個記號,區分開nil,屬於一個存儲中間態
			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.
		if typ != xp.typ {
			panic("sync/atomic: store of inconsistently typed value into Value")
		}
    // 這裏就肯定了,存儲了一種類型,這個永遠只能存儲一個類型。
		StorePointer(&vp.data, xp.data)
		return
	}
}
複製代碼

// 其餘須要掌握的地方

// ifaceWords is interface{} internal representation.
type ifaceWords struct {
	typ  unsafe.Pointer
	data unsafe.Pointer
}
複製代碼
// LoadPointer atomically loads *addr. (原子的加載地址)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) 複製代碼

總結

一、race 和 單例沒有關係,也檢測不出來。

二、race 只是解決讀寫不一致的現場,出現同時讀寫的現象。

相關文章
相關標籤/搜索