通信
共享內存
func Test() {
ordersInfoApp := make([]orderInfoApp, 0, totalCount)
var mux sync.Mutex
wg := sync.WaitGroup{}
for i := 0; i <= 10; i++ {
wg.Add(1)
go func(pageIndex int) {
// do somethine
var ordersInfo orderInfoApp
mux.Lock()
ordersInfoApp = append(ordersInfoApp, ordersInfo)
mux.Unlock()
wg.Done()
}(i)
}
wg.Wait()
}
一般在簡單的數(shù)據(jù)傳遞下使用
channel
func Test() {
ordersInfoApp := make([]orderInfoApp, 0, totalCount)
choi := make(chan orderInfoApp, 10)
wg := sync.WaitGroup{}
for i := 0; i <= 10; i++ {
wg.Add(1)
go func(pageIndex int) {
// do somethine
var ordersInfo orderInfoApp
choi <- ordersInfo
wg.Done()
}(i)
}
go func() {
wg.Wait()
close(choi)
}()
for v := range choi {
ordersInfoApp = append(ordersInfoApp, v)
}
}
相對復雜的數(shù)據(jù)流動情況
同步和控制
goroutine退出只能由本身控制芍秆,不能從外部強制結束該goroutine
兩種例外情況:main函數(shù)結束或者程序崩潰結束運行
共享變量控制結束
func main() {
running := true
f := func() {
for running {
fmt.Println("i am running")
time.Sleep(1 * time.Second)
}
fmt.Println("exit")
}
go f()
go f()
go f()
time.Sleep(2 * time.Second)
running = false
time.Sleep(3 * time.Second)
fmt.Println("main exit")
}
優(yōu)點:
實現(xiàn)簡單拉队,不抽象识虚,方便,一個變量即可簡單控制子goroutine的進行裳擎。
缺點:
結構只能是多讀一寫持隧,不能適應結構復雜的設計谒亦,如果有多寫,會出現(xiàn)數(shù)據(jù)同步問題汗唱,需要加鎖或者使用sync.atomic
不適合用于同級的子go程間的通信宫莱,全局變量傳遞的信息太少
因為是單向通知,主進程無法等待所有子goroutine退出
這種方法只適用于非常簡單的邏輯且并發(fā)量不太大的場景
sync.Waitgroup 等待結束
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// do something
}()
}
wg.Wait()
}
channel控制結束
// 可擴展到多個work
func main() {
chClose := make(chan struct{})
go func() {
for {
select {
case <-chClose:
return
}
// do something
}
}()
//chClose<-struct{}
close(chClose)
}
注意channel的阻塞情況,避免出現(xiàn)死鎖哩罪。
通常channel只能由發(fā)送者關閉
- 向無緩沖的channel寫入數(shù)據(jù)會導致該goroutine阻塞授霸,直到其他goroutine從這個channel中讀取數(shù)據(jù)
- 從無緩沖的channel讀出數(shù)據(jù),如果channel中無數(shù)據(jù)际插,會導致該goroutine阻塞碘耳,直到其他goroutine向這個channel中寫入數(shù)據(jù)
- 向帶緩沖的且緩沖已滿的channel寫入數(shù)據(jù)會導致該goroutine阻塞,直到其他goroutine從這個channel中讀取數(shù)據(jù)
- 向帶緩沖的且緩沖未滿的channel寫入數(shù)據(jù)不會導致該goroutine阻塞
- 從帶緩沖的channel讀出數(shù)據(jù)框弛,如果channel中無數(shù)據(jù)辛辨,會導致該goroutine阻塞,直到其他goroutine向這個channel中寫入數(shù)據(jù)
- 從帶緩沖的channel讀出數(shù)據(jù)瑟枫,如果channel中有數(shù)據(jù)斗搞,該goroutine不會阻塞
// 讀完結束
for {
select {
case <-ch:
default:
goto finish
}
}
finish:
- 如果多個case同時就緒時,select會隨機地選擇一個執(zhí)行
- case標簽里向channel發(fā)送或接收數(shù)據(jù)慷妙,case后面的語句在發(fā)送接收成功后才會執(zhí)行
- nil channel(讀榜旦、寫、讀寫)的case 標簽會被跳過
limitwaitgroup
type limitwaitgroup struct {
sem chan struct{}
wg sync.WaitGroup
}
func New(n int) *limitwaitgroup {
return &limitwaitgroup{
sem: make(chan struct{}, n),
}
}
func (l *limitwaitgroup) Add() {
l.sem <- struct{}{}
l.wg.Add(1)
}
func (l *limitwaitgroup) Done() {
<-l.sem
l.wg.Done()
}
func (l *limitwaitgroup) Wait() {
l.wg.Wait()
}
// 例子
wg := limitwaitgroup.New(6)
for i := 0; i <= 10; i++ {
wg.Add()
go func(index int){
defer wg.Done()
// do something
}(i)
}
wg.Wait()
context
上下文 go 1.7 作為第一個參數(shù)在goroutine里傳遞
Context的接口定義
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline
獲取設置的截止時間(WithDeadline景殷、WithTimeout), 第一個返回值代表截止時間,第二個返回值代表是否設置了截止時間猿挚,false時表示沒有設置截止時間
Done
方法返回一個關閉的只讀的chan咐旧,類型為struct{}
,在goroutine中绩蜻,如果該方法返回的chan可以讀取铣墨,則意味著parent context已經發(fā)起了取消請求,我們通過Done
方法收到這個信號后办绝,就應該做清理操作伊约,然后退出goroutine,釋放資源孕蝉。
Err
context沒有被結束屡律,返回nil;已被結束降淮,返回結束的原因(被取消超埋、超時)。
Value
方法通過一個Key獲取該Context上綁定的值佳鳖,訪問這個值是線程安全的霍殴。key一般定義當前包的一個新的未導出類型的變量(最好不要使用內置類型),避免和其他goroutine的key沖突系吩。
- Context衍生
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
這四個With
函數(shù)来庭,接收的都有一個partent參數(shù),就是父Context穿挨,我們要基于這個父Context創(chuàng)建出子Context的意思月弛,這種方式可以理解為子Context對父Context的繼承,也可以理解為基于父Context的衍生絮蒿。
通過這些函數(shù)尊搬,就創(chuàng)建了一顆Context樹,樹的每個節(jié)點都可以有任意多個子節(jié)點土涝,節(jié)點層級可以有任意多個佛寿。
WithCancel
函數(shù),傳遞一個父Context作為參數(shù)但壮,返回子Context冀泻,以及一個取消函數(shù)用來取消Context。 WithDeadline
函數(shù)蜡饵,和WithCancel
差不多弹渔,它會多傳遞一個截止時間參數(shù),意味著到了這個時間點溯祸,會自動取消Context肢专,也可以不等到這個時候舞肆,可以提前通過取消函數(shù)進行取消。
WithTimeout
和WithDeadline
基本上一樣博杖,這個表示是超時自動取消椿胯,設置多少時間后自動取消Context。
WithValue
函數(shù)和取消Context無關剃根,生成一個綁定了一個鍵值對數(shù)據(jù)的Context哩盲,這個綁定的數(shù)據(jù)可以通過Context.Value
方法訪問到
- 例子
WithCancel
func main() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx, "goroutine 1")
go watch(ctx, "goroutine 2")
go watch(ctx, "goroutine 3")
time.Sleep(10 * time.Second)
fmt.Println("開始結束goroutine")
cancel()
time.Sleep(5 * time.Second)
fmt.Println(ctx.Err())
}
func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "over")
return
default:
fmt.Println(name, "running")
time.Sleep(2 * time.Second)
}
}
}
// output:
goroutine 1 running
goroutine 2 running
goroutine 3 running
goroutine 1 running
goroutine 2 running
goroutine 3 running
goroutine 1 running
goroutine 2 running
goroutine 3 running
goroutine 2 running
goroutine 3 running
goroutine 1 running
goroutine 3 running
goroutine 2 running
goroutine 1 running
開始結束goroutine
goroutine 1 over
goroutine 2 over
goroutine 3 over
context canceled
WithDeadline
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
go watch(ctx, "goroutine 1")
go watch(ctx, "goroutine 2")
go watch(ctx, "goroutine 3")
_ = cancel
time.Sleep(8 * time.Second)
fmt.Println(ctx.Err())
}
func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "over")
return
default:
fmt.Println(name, "running")
time.Sleep(2 * time.Second)
}
}
}
// output:
goroutine 3 running
goroutine 2 running
goroutine 1 running
goroutine 3 running
goroutine 1 running
goroutine 2 running
goroutine 1 running
goroutine 3 running
goroutine 2 running
goroutine 3 over
goroutine 1 over
goroutine 2 over
context deadline exceeded
WithTimeout
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
go watch(ctx, "goroutine 1")
go watch(ctx, "goroutine 2")
go watch(ctx, "goroutine 3")
_ = cancel
time.Sleep(8 * time.Second)
fmt.Println(ctx.Err())
}
func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "over")
return
default:
fmt.Println(name, "running")
time.Sleep(2 * time.Second)
}
}
}
// output:
goroutine 3 running
goroutine 1 running
goroutine 2 running
goroutine 3 running
goroutine 2 running
goroutine 1 running
goroutine 2 running
goroutine 1 running
goroutine 3 running
goroutine 2 over
goroutine 3 over
goroutine 1 over
context deadline exceeded
WithValue
type key int // 未導出的包私有類型
var kkk key = 0
func main() {
ctx, cancel := context.WithCancel(context.Background())
// WithValue是沒有取消函數(shù)的
ctx = context.WithValue(ctx, kkk, "100W")
go watch(ctx, "goroutine 1")
go watch(ctx, "goroutine 2")
go watch(ctx, "goroutine 3")
time.Sleep(8 * time.Second)
fmt.Println("開始結束goroutine")
cancel()
time.Sleep(3 * time.Second)
fmt.Println(ctx.Err())
}
func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "over")
return
default:
fmt.Println(name, "running", "爸爸給我了", ctx.Value(kkk).(string))
time.Sleep(2 * time.Second)
}
}
}
// output:
goroutine 1 running 爸爸給我了 100W
goroutine 2 running 爸爸給我了 100W
goroutine 3 running 爸爸給我了 100W
goroutine 2 running 爸爸給我了 100W
goroutine 1 running 爸爸給我了 100W
goroutine 3 running 爸爸給我了 100W
goroutine 1 running 爸爸給我了 100W
goroutine 2 running 爸爸給我了 100W
goroutine 3 running 爸爸給我了 100W
goroutine 1 running 爸爸給我了 100W
goroutine 3 running 爸爸給我了 100W
goroutine 2 running 爸爸給我了 100W
開始結束goroutine
goroutine 2 over
goroutine 3 over
goroutine 1 running 爸爸給我了 100W
goroutine 1 over
context canceled
控制多個goroutine
func main() {
http.HandleFunc("/", func(W http.ResponseWriter, r *http.Request) {
fmt.Println("收到請求")
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx, 2)
go worker(ctx, 3)
time.Sleep(time.Second * 10)
cancel()
fmt.Println(ctx.Err())
})
http.ListenAndServe(":9290", nil)
}
func worker(ctx context.Context, speed int) {
reader := func(n int) {
for {
select {
case <-ctx.Done():
return
default:
break
}
fmt.Println("reader:", n)
time.Sleep(time.Duration(n) * time.Second)
}
}
go reader(2)
go reader(1)
for {
select {
case <-ctx.Done():
return
default:
break
}
fmt.Println("worker:", speed)
time.Sleep(time.Duration(speed) * time.Second)
}
}
// output:
收到請求
worker: 2
reader: 1
worker: 3
reader: 1
reader: 2
reader: 2
reader: 1
reader: 1
reader: 1
reader: 2
worker: 2
reader: 2
reader: 1
reader: 1
reader: 1
worker: 3
reader: 1
worker: 2
reader: 2
reader: 1
reader: 2
reader: 1
context canceled
-
使用規(guī)則
使用Context的程序應遵循以下這些規(guī)則來保持跨包接口的一致和方便靜態(tài)分析工具(go vet)來檢查上下文傳播是否有潛在問題。- 不要將Context存儲在結構類型中狈醉,而是顯式的傳遞給每個需要的函數(shù)廉油; Context應該作為函數(shù)的第一個參數(shù)傳遞,通常命名為 ctx:
func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
即使函數(shù)可以接受nil值苗傅,也不要傳遞nil Context抒线。如果不確定要使用哪個Context,請傳遞 context.TODO金吗。
使用context的Value相關方法只應該用于在程序和接口中傳遞和請求相關的元數(shù)據(jù)十兢,不要用它來傳遞一些可選的參數(shù)
相同的Context可以傳遞給在不同 goroutine 中運行的函數(shù); Context對于多個 goroutine 同時使用是安全的。
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布摇庙!