go channel詳解

協(xié)程张症,通道

  • 我們在普通程序中要執(zhí)行代碼如下代碼
func main() {
    foo()
    bar()
}
func foo() {
    for i := 0; i < 45; i++ {
        fmt.Println("Foo:", i)
    }
}
func bar() {
    for i := 0; i < 45; i++ {
        fmt.Println("Bar:", i)
    }
}
得到結(jié)果
Foo: 41
Foo: 42
Foo: 43
Foo: 44
Bar: 0
Bar: 1
Bar: 2
Bar: 3
這個是按順序執(zhí)行的
  • 錯誤使用協(xié)程
package main
import "fmt"
func main() {
    go foo()
    go bar()
}
func foo() {
    for i := 0; i < 45; i++ {
        fmt.Println("Foo:", i)
    }
}
func bar() {
    for i := 0; i < 45; i++ {
        fmt.Println("Bar:", i)
    }
}

由于沒有調(diào)度,主協(xié)程率先執(zhí)行完畢鸵贬,代碼執(zhí)行已經(jīng)關(guān)閉俗他,所以協(xié)程根本沒有執(zhí)行。

  • 正確使用協(xié)程
package main
import (
    "fmt"
    "sync"
)
var wg sync.WaitGroup
func main() {
    wg.Add(2)
    go foo()
    go bar()
    wg.Wait()
}
func foo() {
    for i := 0; i < 45; i++ {
        fmt.Println("Foo:", i)
    }
    wg.Done()
}
func bar() {
    for i := 0; i < 45; i++ {
        fmt.Println("Bar:", i)
    }
    wg.Done()
}

用sync.WaitGroup阔逼,可以阻塞主線程兆衅,wg.Add(2)加入兩個子線程,wg.Wait()一直等待嗜浮,每當一個執(zhí)行完成調(diào)用函數(shù)wg.Done()將一個線程去掉羡亩,直到全部執(zhí)行完子線程∥H冢‘’

  • 協(xié)程:正如官方所言畏铆,goroutine 是一個輕量級的執(zhí)行單元,相比線程開銷更小吉殃,完全由 Go 語言負責調(diào)度辞居,是 Go 支持并發(fā)的核
    心。開啟一個 goroutine 非常簡單:

  • 協(xié)程的運行

package main
   import (
    "fmt"
    "time"
)
func main() {
    go fmt.Println("goroutine message")
    time.Sleep(1) //1
    fmt.Println("main function message")
}
  • 協(xié)程的好處蛋勺,上面的例子我們沒有看到協(xié)程究竟能有什么好處
package main
import (
    "fmt"
    "sync"
    "time"
)
var wg sync.WaitGroup

func main() {
    wg.Add(3)
    go foo()
    go bar()
    go dar()
    wg.Wait()
}
func foo() {
    for i := 0; i < 45; i++ {
        fmt.Println("Foo:", i)
        time.Sleep(3 * time.Millisecond)
    }
    wg.Done()
}
func bar() {
    for i := 0; i < 45; i++ {
        fmt.Println("Bar:", i)
        time.Sleep(20 * time.Millisecond)
    }
    wg.Done()
}
func dar() {
    for i := 0; i < 45; i++ {
        fmt.Println("dar:", i)
    }
    wg.Done()
}

從上面的代碼執(zhí)行效果看到瓦灶,協(xié)程能夠使我們,遇到阻塞抱完,不會阻塞到那贼陶,另一個協(xié)程還會繼續(xù)執(zhí)行。

  • 在前面我們只是用了系統(tǒng)中的單核,go其實是一門異步多線程語言
package main
import (
    "fmt"
    "runtime"
    "sync"
    "time"
)
var wg sync.WaitGroup

func init() {
    runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
    wg.Add(2)
    go foo()
    go bar()
    wg.Wait()
}
func foo() {
    for i := 0; i < 45; i++ {
        fmt.Println("Foo:", i)
        time.Sleep(3 * time.Millisecond)
    }
    wg.Done()
}
func bar() {
    for i := 0; i < 45; i++ {
        fmt.Println("Bar:", i)
        time.Sleep(20 * time.Millisecond)
    }
    wg.Done()
}

當某個協(xié)程阻塞時每界,cpu會把其他協(xié)程加載進來執(zhí)行捅僵,這樣確保cpu不會空閑著≌2悖 runtime.GOMAXPROCS(runtime.NumCPU())表示在系統(tǒng)的所有核上并發(fā)運行庙楚。

  • 競爭當用同一個變量時,該變量會使該變量變化趴樱。
package main
import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
var wg sync.WaitGroup
var counter int
func main() {
    wg.Add(2)
    go incrementor("Foo:")
    go incrementor("Bar:")
    wg.Wait()
    fmt.Println("Final Counter:", counter)
}
func incrementor(s string) {
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < 20; i++ {
        x := counter
        x++
        time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
        counter = x
        fmt.Println(s, i, "Counter:", counter)
    }
    wg.Done()
}
  • 加鎖
package main
import (
   "fmt"
   "math/rand"
   "sync"
   "time"
)
var wg sync.WaitGroup
var counter int
var mutex sync.Mutex
func main() {
   wg.Add(2)
   go incrementor("Foo:")
   go incrementor("Bar:")
   wg.Wait()
   fmt.Println("Final Counter:", counter)
}
func incrementor(s string) {
   for i := 0; i < 20; i++ {
       time.Sleep(time.Duration(rand.Intn(20)) * time.Millisecond)
       mutex.Lock()
       counter++
       fmt.Println(s, i, "Counter:", counter)
       mutex.Unlock()
   }
   wg.Done()
}

  • 原子操作
package main

import (
   "fmt"
   "math/rand"
   "sync"
   "sync/atomic"
   "time"
)
var wg sync.WaitGroup
var counter int64
func main() {
   wg.Add(2)
   go incrementor("Foo:")
   go incrementor("Bar:")
   wg.Wait()
   fmt.Println("Final Counter:", counter)
}
func incrementor(s string) {
   for i := 0; i < 20; i++ {
       time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
       atomic.AddInt64(&counter, 1)
       fmt.Println(s, i, "Counter:", atomic.LoadInt64(&counter)) // access without race
   }
   wg.Done()
}

// go run -race main.go
// vs
// go run main.go

  • 為什么要需要channel
    time.Sleep(1) 這是為了讓新開啟的 goroutine 有機會得到執(zhí)行馒闷,開啟一個 goroutine 之后,后續(xù)的代碼會繼續(xù)執(zhí)行叁征,在上面的例子中后續(xù)代碼執(zhí)行完畢程序就終止了纳账,而開啟的 goroutine 可能還沒開始執(zhí)行。
    如果嘗試去掉 #1 處的代碼捺疼,程序也可能會正常運行疏虫,這是因為恰巧開啟的 goroutine 只是簡單的執(zhí)行了一次輸出,如果 goroutine 中耗時稍長就會導致只能看到主一句 main function message 啤呼。
    換句話話說卧秘,這里的 time.sleep 提供的是一種調(diào)度機制,這也是 Go 中 channel 存在的目的:負責消息傳遞和調(diào)度官扣。

  • 什么是channel
    Channel 是 Go 中為 goroutine 提供的一種通信機制翅敌,channel 是有類型的,而且是有方向的惕蹄,可以把 channel 類比成 unix
    中的 pipe蚯涮。channel 是用來傳遞消息的。

package main
import (
   "fmt"
 )
func main() {
 i := make(chan int)//int 類型
 s := make(chan string)//字符串類型
 r := make(<-chan bool)//只讀
 w := make(chan<- []int)//只寫
   c := make(chan int)
   go func() {
       fmt.Println("goroutine message")
       c <- 1 //1
   }()
   <-c //2
   fmt.Println("main function message")
}

聲明了一個 int 類型的 channel卖陵,在 goroutine 中在代碼 #1 處向 channel 發(fā)送了數(shù)據(jù) 1 遭顶,在 main 中 #2 處等待數(shù)據(jù)的接收,如果 c 中沒有數(shù)據(jù)泪蔫,代碼的執(zhí)行將發(fā)生阻塞液肌,直到 c 中數(shù)據(jù)接收完畢。這是 channel 最簡單的用法之一:同步 鸥滨,這種類型的 channel 沒有設(shè)置容量,稱之為 unbuffered channel谤祖。
發(fā)送與等待互相阻塞婿滓,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粥喜,隨后出現(xiàn)的幾起案子凸主,更是在濱河造成了極大的恐慌,老刑警劉巖额湘,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卿吐,死亡現(xiàn)場離奇詭異旁舰,居然都是意外死亡,警方通過查閱死者的電腦和手機嗡官,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門箭窜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衍腥,你說我怎么就攤上這事磺樱。” “怎么了婆咸?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵竹捉,是天一觀的道長。 經(jīng)常有香客問我尚骄,道長块差,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任倔丈,我火速辦了婚禮憨闰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乃沙。我一直安慰自己起趾,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布警儒。 她就那樣靜靜地躺著训裆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜀铲。 梳的紋絲不亂的頭發(fā)上边琉,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音记劝,去河邊找鬼变姨。 笑死,一個胖子當著我的面吹牛厌丑,可吹牛的內(nèi)容都是我干的定欧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼怒竿,長吁一口氣:“原來是場噩夢啊……” “哼砍鸠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起耕驰,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤爷辱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饭弓,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡双饥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了弟断。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咏花。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夫嗓,靈堂內(nèi)的尸體忽然破棺而出迟螺,到底是詐尸還是另有隱情,我是刑警寧澤舍咖,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布矩父,位于F島的核電站,受9級特大地震影響排霉,放射性物質(zhì)發(fā)生泄漏窍株。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一攻柠、第九天 我趴在偏房一處隱蔽的房頂上張望球订。 院中可真熱鬧,春花似錦瑰钮、人聲如沸冒滩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽开睡。三九已至,卻和暖如春苟耻,著一層夾襖步出監(jiān)牢的瞬間篇恒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工凶杖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胁艰,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓智蝠,卻偏偏與公主長得像腾么,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子杈湾,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Goroutine是Go里的一種輕量級線程——協(xié)程解虱。相對線程,協(xié)程的優(yōu)勢就在于它非常輕量級毛秘,進行上下文切換的代價非...
    witchiman閱讀 4,815評論 0 9
  • 原文鏈接:https://github.com/EasyKotlin 在常用的并發(fā)模型中,多進程、多線程叫挟、分布式是...
    JackChen1024閱讀 10,702評論 3 23
  • 本文主要介紹協(xié)程的用法, 以及使用協(xié)程能帶來什么好處. 另外, 也會粗略提一下協(xié)程的大致原理.本文的意義可能僅僅是...
    登高而望遠閱讀 35,177評論 18 140
  • GO并發(fā)使用goroutine運行程序艰匙,檢測并修正狀態(tài),利用通道共享數(shù)據(jù)抹恳。通常程序會被編寫為一個順序執(zhí)行并完成一個...
    小線亮亮閱讀 544評論 0 1
  • 多進程/線程 最早的服務(wù)器端程序都是通過多進程员凝、多線程來解決并發(fā)IO的問題。進程模型出現(xiàn)的最早奋献,從Unix 系統(tǒng)誕...
    Newt0n閱讀 15,245評論 9 69