Golang - sync包

​ Golang的 sync包提供了大量的線程同步操做的類. 因此知足咱們平常使用. 包含鎖,和安全的集合. 其中,關於golang 各類鎖的實現,請看這個文章,我我的感受寫的不錯juejin.im/entry/5ed32…java

sync.Mutex

第一種就是 Mutex 爲互斥鎖 , 實現跟Java的reentrantlock很像. 基本都是自旋鎖,golang

咱們下面有一個場景 , 將變量x累加50000次 咱們開啓5個goroutine去執行安全

func main() {
	var x = 0
	for num := 0; num < 4; num++ {
		go func() {
			for i := 0; i < 10000; i++ {
				increase(&x)
			}
		}()
	}
	// 等待子線程退出
	time.Sleep(time.Millisecond * 2000)
	fmt.Println(x)
}
// 這個操做是Java作不了的.只有C,C++和Golang能夠
func increase(x *int) {
	*x = *x + 1
}
複製代碼

若是輸出結果是 50000那就對了, 咱們看看結果多線程

23283
複製代碼

爲何呢, 由於多線程同時操做一個變量時就會出現這種問題 , 出現讀寫不一致的問題 , 如何處理呢函數

咱們申明一個鎖,讓他同步執行 , 這麼申明就能夠了ui

var ILock = sync.Mutex{}

func main() {
	var x = 0
	for num := 0; num < 5; num++ {
		go func() {
			for i := 0; i < 10000; i++ {
				increase(&x)
			}
		}()
	}

	// 等待子線程退出
	time.Sleep(time.Millisecond * 2000)
	fmt.Println(x)
}

func increase(x *int) // 1. 先加鎖 ILock.Lock() // 執行完方法釋放鎖. 跟Java的很像,可是不是reentrant defer ILock.Unlock() *x = *x + 1 } 複製代碼

此時執行 發現結果spa

50000
複製代碼

輸出正確.線程

其實還能夠這麼寫. 效率更高指針

go func() {
    ILock.Lock()
    defer ILock.Unlock()
    for i := 0; i < 10000; i++ {
        increase(&x)
    }
}()
複製代碼

利用Chan阻塞實現鎖

var (
	lock = make(chan int, 1)
)

func Lock() {
	lock <- 1
}
func Unlock() {
	<-lock
}
複製代碼

此時這就是一個最簡單的鎖.code

sync.RWMutex 讀寫鎖

​ 跟Java的如出一轍 , 我就很少說了, 讀寫互斥, 可多讀(讀鎖能夠同時存在多個), 但讀寫不能同時存在, 寫互斥

func main() {
	// 讀寫鎖
	mutex := sync.RWMutex{}

	go func() {
		// 讀鎖
		mutex.RLocker().Lock()
		fmt.Println("讀")
		mutex.RLocker().Unlock()
	}()
	go func() {
		mutex.RLocker().Lock()
		fmt.Println("讀")
		time.Sleep(time.Millisecond * 5000)
		mutex.RLocker().Unlock()
	}()

	time.Sleep(time.Millisecond * 1000)

	go func() {
		// 寫鎖 , 就是普通的鎖了
		fmt.Println("拿到了")
		mutex.Lock()
		fmt.Println("寫")
		mutex.Unlock()
	}()
	time.Sleep(time.Millisecond * 6000)
}
複製代碼

輸出

讀
讀
拿到了 // 耗時1ms// 對比前一步耗時 ms
複製代碼

sync.WaitGroup

​ 這個類相似於Java的CountDownLatch 類, 可是比Java的好點. 第一Java的須要初始化告訴他計數器是多少, 因此他只有一個countdown和wait操做.

​ 可是 sync.WaitGroup 倒是三個操做, 第一個 down , add(int) , wait 三個操做. 因此他實現更加好.

簡單以第一個例子爲例子吧, 好比說 , 咱們不知道goroutine啥時候退出是吧, 可是有了這玩意就知道了.

func main() {
	var WG = sync.WaitGroup{}

	start := time.Now().UnixNano() / 1e6
	for num := 0; num < 5; num++ {
		WG.Add(1)
		go func(num int) {
			defer WG.Done()
			ran := rand.Int63n(1000)
			fmt.Printf("goroutine-%d sleep : %dms\n", num, ran)
			time.Sleep(time.Millisecond * time.Duration(ran))
		}(num)
	}
	// 等待子線程退出
	WG.Wait()
	fmt.Printf("main waitting : %dms\n", time.Now().UnixNano()/1e6-start)
}
複製代碼

輸出 : 根據木桶原理, 耗時最長的是937ms, 因此主線程等待了939ms.

goroutine-1 sleep : 410ms
goroutine-0 sleep : 821ms
goroutine-2 sleep : 51ms
goroutine-3 sleep : 937ms
goroutine-4 sleep : 551ms
main waitting : 939ms
複製代碼

注意點 :

​ 因爲sync.WaitGroup也是一個對象Structs, 因此須要指針傳遞, 不能使用值傳遞, 注意一下.由於狀態值複製了就無效了.

根據封裝, 咱們須要傳遞 sync.waitgroup .

func fun(num int, wg *sync.WaitGroup) {
	defer wg.Done()
	ran := rand.Int63n(1000)
	fmt.Printf("goroutine-%d sleep : %dms\n", num, ran)
	time.Sleep(time.Millisecond * time.Duration(ran))
}
複製代碼

而後main方法

func main() {
	var WG = &sync.WaitGroup{}
	start := time.Now().UnixNano() / 1e6

	for num := 0; num < 5; num++ {
		WG.Add(1)
		go fun(num, WG)
	}

	// 等待子線程退出
	WG.Wait()
	fmt.Printf("main waitting : %dms\n", time.Now().UnixNano()/1e6-start)
}
複製代碼

輸出 :

goroutine-4 sleep : 410ms
goroutine-1 sleep : 551ms
goroutine-0 sleep : 821ms
goroutine-2 sleep : 51ms
goroutine-3 sleep : 937ms
main waitting : 939ms
複製代碼

sync.Once 一次操做

這個玩意可讓 once.Do() 方法只執行一次. 其實相似於一個flag,一開始爲true. 每次執行判斷是否爲true , 當執行了一次之後改爲false. 其實他就是這個原理 , 不過他使用了cas , 保證了線程安全性,

func main() {
	once := sync.Once{}
	one(&once)
	one(&once)
	one(&once)
}

func one(once *sync.Once)  {
	fmt.Println("執行函數")
	once.Do(func() {
		fmt.Println("只會執行了一次")
	})
}
複製代碼

輸出

執行函數
只會執行了一次
執行函數
執行函數
複製代碼

因此他能夠只執行一次 , 適合作初始化操做, 或者其餘一次性的操做, 不須要屢次

sync.Map

提供的線程安全的map , 多線程訪問時, 對於crud 操做, 會加鎖.

maps := sync.Map{}
// 存
maps.Store("","")
// 刪
maps.Delete("")
// 取
maps.Load("")

// 有就不存,返回已經存了的對象和true, 若是沒有就返回存的value和false.
maps.LoadOrStore("","")
複製代碼

sync.Pool

​ 顧名思義一個池子, 那麼咱們看看這個池子主要作啥了.

Pool用於存儲那些被分配了可是沒有被使用,而將來可能會使用的值,以減少垃圾回收的壓力。(適合大對象)

​ 同時他提供了一個存儲的地方. 減小大量實例化過程 . 可是效率未必要比實例化快奧 . 由於維護一個對象要考慮各類問題, 這就是效率 , 可是實例化啥也不用考慮.

操做很簡單.

func main() {

	pool := &sync.Pool{
		New: func() interface{} {
			return "new"
		},
	}
	
	// 首先的放一個 , 因爲put操做.
	pool.Put("init")

	go func() {
		// 拿一個
		s := pool.Get().(string)
		// 使用
		fmt.Println(s)
		// 使用完放進去, 因此特別適合大對象. 
		pool.Put(s)
	}()
}
複製代碼

sync.Cond

相似與Java的wait和notify 或者說 Condition ,更像後者,可是沒有超時的機制

func main() {
	mutex := sync.Mutex{}
	start := sync.NewCond(&mutex)

	for x := 0; x < 10; x++ {
		go func() {
			start.L.Lock()
			defer start.L.Unlock()
			start.Wait()
			fmt.Println("do work")
		}()
	}

	go func() {
		time.Sleep(time.Second * 1)
		start.Broadcast()
	}()

	time.Sleep(time.Second * 2)
}
複製代碼
相關文章
相關標籤/搜索