goroutine并發(fā)控制

通信

共享內存

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,釋放資源孕蝉。

Errcontext沒有被結束屡律,返回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ù)進行取消。

WithTimeoutWithDeadline基本上一樣博杖,這個表示是超時自動取消椿胯,設置多少時間后自動取消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ā)布摇庙!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末旱物,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卫袒,更是在濱河造成了極大的恐慌宵呛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夕凝,死亡現(xiàn)場離奇詭異宝穗,居然都是意外死亡,警方通過查閱死者的電腦和手機码秉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門逮矛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人转砖,你說我怎么就攤上這事须鼎。” “怎么了府蔗?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵晋控,是天一觀的道長。 經常有香客問我姓赤,道長赡译,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任不铆,我火速辦了婚禮蝌焚,結果婚禮上裹唆,老公的妹妹穿的比我還像新娘。我一直安慰自己只洒,他們只是感情好品腹,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著红碑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泡垃。 梳的紋絲不亂的頭發(fā)上析珊,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音蔑穴,去河邊找鬼忠寻。 笑死,一個胖子當著我的面吹牛存和,可吹牛的內容都是我干的奕剃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼捐腿,長吁一口氣:“原來是場噩夢啊……” “哼纵朋!你這毒婦竟也來了?” 一聲冷哼從身側響起茄袖,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤操软,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宪祥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂薪,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年蝗羊,在試婚紗的時候發(fā)現(xiàn)自己被綠了藏澳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡耀找,死狀恐怖翔悠,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情涯呻,我是刑警寧澤凉驻,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站复罐,受9級特大地震影響涝登,放射性物質發(fā)生泄漏。R本人自食惡果不足惜效诅,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一胀滚、第九天 我趴在偏房一處隱蔽的房頂上張望趟济。 院中可真熱鬧,春花似錦咽笼、人聲如沸顷编。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媳纬。三九已至,卻和暖如春施掏,著一層夾襖步出監(jiān)牢的瞬間钮惠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工七芭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留素挽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓狸驳,卻偏偏與公主長得像预明,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耙箍,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • 開發(fā)go程序的時候撰糠,時常需要使用goroutine并發(fā)處理任務,有時候這些goroutine是相互獨立的究西,而有的時...
    駐馬聽雪閱讀 2,427評論 0 21
  • 本文從上下文Context窗慎、同步原語與鎖、Channel卤材、調度器四個方面介紹Go語言是如何實現(xiàn)并發(fā)的遮斥。本文絕大部分...
    彥幀閱讀 1,558評論 1 3
  • 模擬一下協(xié)程數(shù)量太多的危害: number過大,服務器系統(tǒng)資源利用率不斷上漲扇丛,到最后程序自動killed 協(xié)程池解...
    超鴿帶你飛閱讀 212評論 0 0
  • 并發(fā)的概念及其重要性 這段是簡單科普帆精,大佬可以跳過 并發(fā):并發(fā)程序指同時進行多個任務的程序较屿。在操作系統(tǒng)中,是指一個...
    亦一銀河閱讀 733評論 0 1
  • 控制并發(fā)有三種種經典的方式卓练,一種是通過channel通知實現(xiàn)并發(fā)控制 一種是WaitGroup隘蝎,另外一種就是Con...
    wiseAaron閱讀 10,640評論 4 34