Golang最爲讓人熟知的併發模型當屬CSP併發模型,也就是由goroutine和channel構成的GMP併發模型,具體內容不在贅述了,能夠翻回以前的文章查看。在這裏,要講講Golang的其餘併發方式。git
Golang不只可使用CSP併發模式,還可使用傳統的共享數據的併發模式。github
這是傳統語言比較經常使用的的方式,即加鎖。加鎖使其線程同步,每次只容許一個goroutine進入某個代碼塊,此代碼塊區域稱之爲"臨界區(critical section)」。golang
Golang爲*臨界區(critical section)*提供的是互斥鎖的包和條件變量的包。安全
就是一般使用的鎖,用來讓線程串行用的。Golang提供了互斥鎖sync.Mutex
和讀寫互斥鎖 sync.RWMutex
,用法極其簡單:bash
var s sync.Mutex
s.Lock()
// 這裏的代碼就是串行了,吼吼吼。。。
s.Unlock()
複製代碼
Lock和Unlock微信
sync.Mutex
和sync.RWMutex
的區別沒啥大的區別,只不過sync.RWMutex
更加細膩,能夠將「讀操做」和「寫操做」區別對待。 sync.RWMutex
中的Lock和unLock針對寫操做併發
var s sync.RWMutex
s.Lock()
// 上寫鎖了,吼吼
s.Unlock()
複製代碼
sync.RWMutex
中的RLock和RUnLock針對讀操做函數
var s sync.RWMutex
s.RLock()
// 上讀鎖了,吼吼..
s.RUnlock()
複製代碼
讀寫鎖有如下規則:ui
即:多個寫操做不能同時進行,寫操做和讀操做也不能同時進行,多個讀操做能夠同時進行atom
recover
函數對它們起不到任何做用。一旦產生死鎖,程序必然崩潰。defer
語句來解鎖;可是,必定不要對未鎖定的或者已經鎖定的互斥鎖解鎖,由於會觸發panic
,並且此panic
和死鎖同樣,屬於致命錯誤,程序確定崩潰sync.Mutex
是個結構體,儘可能不要其當作參數,在多個函數直接傳播。由於沒啥意義,Golang的參數都是副本,多個副本之間都是相互獨立的。互斥鎖是用來鎖住資源,「創造」臨界區的。而條件變量Cond能夠認爲是用來自行調度線程(在此即爲groutine)的,當某個狀態時,阻塞等待,當狀態改變時,喚醒。
Cond的使用,離不開互斥鎖,即離不開sync.Mutex
和sync.RWMutex
。 Cond初始化都須要有個互斥鎖。(ps:哪怕初始化不須要,就應用場景而言,也得須要個互斥鎖)
Cond
提供Wait、Signal、Broadcast 三種方法。 Wait表示線程(groutine)阻塞等待; Signal表示喚醒等待的groutine; Broadcast表示喚醒等待的全部groutine;
初始化:
cond := sync.NewCond(&sync.Mutex{})
複製代碼
在其中一個groutine中:
cond.L.Lock()
for status == 0 {
cond.Wait()
}
//狀態改變,goroutine被喚醒後,乾點啥。。。
cond.L.Unlock()
複製代碼
以上算是模板
在另一個groutine中:
cond.L.Lock()
status = 1
cond.Signal() // 或者使用cond.Broadcast()來喚醒以上groutine中沉睡的groutine
cond.L.Unlock()
複製代碼
原子操做是硬件芯片級別的支持,因此能夠保證絕對的線程安全。並且執行效率比其餘方式要高出好幾個數量級。
Go語言的原子操做固然也是基於CPU和操做系統的,Go語言提供的原子操做的包是sync/atomic
,此包提供了加(Add)、CAS(交換並比較 compare and swap)、成對出現的存儲(store)和加載(load)以及交換(swap)。
此包提供的大多數函數針對的數據類型也很是的單一:只有整型!使用方式十分的簡單,看着函數直接調用就好。
var a int32
a = 1
a = atomic.AddInt32(&a, 2) //此處是原子操做,就這麼簡單,吼吼
複製代碼
在此特別強調一下CAS,CAS對應的函數前綴是「CompareAndSwap」,含義和用法正如英文翻譯:比較並交換。在進行CAS操做的時候,函數會先判斷被操做變量的當前值是否與咱們預期的舊值相等,若是相等,它就把新值賦給該變量,並返回true,反之,就忽略此操做,並返回false。
多是Golang提供的原子操做的數據類型實在是有限,Go又補充了一個結構體atomic.Value
,此結構體至關於一個小容器,能夠提供原子操做的存儲store
和提取load
var atomicVal atomic.Value
str := "hello"
atomicVal.Store(str) //此處是原子操做哦
newStr := atomicVal.Load() //此處是原子操做哦
複製代碼
爲了能更好的調度goroutine,Go提供了sync.WaitGroup
、sync.Once
還有context
sync.WaitGroup
sync.WaitGroup
的做用就是在多goroutine併發程序中,讓主goroutine等待全部goroutine執行結束。(直接查看代碼註釋) sync.WaitGroup
提供了三個函數Add
、Done
和Wait
三者用法以下:
var wait sync.WaitGroup
wait.Add(2) //必須是運行的goroutine的數量
go func() {
//TODO 一頓小操做
defer wait.Done() // done函數用在goroutine中,表示goroutine操做結束
}()
go func() {
//TODO 一頓小操做
defer wait.Done() // done函數用在goroutine中,表示goroutine操做結束
}()
wait.Wait() // block住了,直到全部goroutine都結束
複製代碼
sync.WaitGroup
中有一個計數器,記錄的是須要等待的goroutine的數量,默認值是0,能夠經過Add方法來增長或者減小值,可是切記,千萬不能讓計數器的值小於零,會觸發panic!
sync.WaitGroup
調用Wait方法的時候,sync.WaitGroup
中計數器的值必定要爲0。所以Add中的值必定要等於非主goroutine的數量! 且不要把Add和Wait方法放到不一樣的goroutine中執行!
sync.Once
真真正正的只執行一次。
sync.Once
只要一個方法:Do
,裏面就一個參數:func
。多說無益,複製下面代碼,猜猜執行結果就知道了。
package main
import (
"fmt"
"sync"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
}
複製代碼
執行結果
Only once
複製代碼
沒錯,只有一行。真只執行了一次。
context能夠用來實現一對多的goroutine協做。這個包的應用場景主要是在API中。字面意思也很直接,上下文。當一個請求來時,會產生一個goroutine,可是這個goroutine每每要衍生出許多額外的goroutine去處理操做,例如連接database、請求rpc請求。。等等,這些衍生的goroutine和主goroutine有不少公用數據的,例如同一個請求生命週期、用戶認證信息、token等,當這個請求超時或者被取消的時候,這裏全部的goroutine都應該結束。context就能夠幫助咱們達到這個效果。
很顯然,主goroutine和衍生的全部子goroutine之間造成了一顆樹結構。咱們的context能夠從根節點遍及整棵樹,固然,是線程安全的。
線程之間的基本是這樣的:
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
複製代碼
有兩個根context:background和todo;這兩個根都是contenxt空的,沒有值的。二者也沒啥太本質的區別,Background是最經常使用的,做爲Context這個樹結構的最頂層的Context,它不能被取消。當不知道用啥context的時候就能夠用TODO。
根生成子節點有如下方法:
//生成可撤銷的Context (手動撤銷)
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) //生成可定時撤銷的Context (定時撤銷) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //也是生成可定時撤銷的Context (定時撤銷) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) //不可撤銷的Context,能夠存一個kv的值 func WithValue(parent Context, key, val interface{}) Context 複製代碼
如下是每一個方法的調用方式(全都來自godoc,可粘貼複用): 可撤銷的func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done(): //只有撤銷函數被調用後,纔會觸發
return
case dst <- n:
n++
}
}
}()
return dst
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() //調用返回的cancel方法來讓 context聲明週期結束
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
複製代碼
要想結束全部線程,就調用ctx, cancel := context.WithCancel(context.Background())
函數返回的cancel函數便可,當撤銷函數被調用以後,對應的Context值會先關閉它內部的接收通道,也就是它的Done方法返回的通道。
WithDeadline
和WithTimeout
用法基本相似,並且WithTimeout函數內部調用了WithDeadline函數。二者惟一區別是WithTimeout表示從如今開始xxx超時,而WithDeadline的時間能夠是以前的時間:意思是說WithTimeout表示從如今開始, xxx時間後超時。而WithDeadline表示xx時間點,結束!這個時間點能夠是昨天,時間點不收任何限制。
如下是godoc給出的列子:
WithDeadline
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancelation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel() //時間超時會自動調用
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
複製代碼
輸出:
context deadline exceeded
複製代碼
WithTimeout
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel() //時間超時會自動調用
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
複製代碼
輸出:
context deadline exceeded
複製代碼
WithValue
能夠用來在傳遞值的,值的存取是以KV的形式來進行的。直接上例子
type favContextKey string
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("key not found:", k)
}
k := favContextKey("language")
k1 := favContextKey("Chinese")
ctx := context.WithValue(context.Background(), k, "Go")
ctx1 := context.WithValue(ctx, k1, "Go1")
f(ctx1, k1)
f(ctx1, k)
複製代碼
輸出:
found value: Go1
found value: Go
複製代碼
互聯網技術窩
或者加微信共同探討交流: