您好,登錄后才能下訂單哦!
這篇文章給大家介紹如何在go中利用協程對返回值進行處理,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
package main import "fmt" import "sync" var ch = make(chan int) func do(lock *sync.Mutex, ct *int) { lock.Lock() *ct++ lock.Unlock() ch <- 1 } func main() { fmt.Println("hello thread") var ct = 0 lock := &sync.Mutex{} for i:=0; i<10; i++ { go do(lock, &ct) } for i:=0; i<10; i++ { <- ch } fmt.Println("ct=", ct) }
輸出: 10
補充:Goroutine協程之間的數據溝通的方式
一個服務器物理線程能夠跑多個goroutine,成千上萬個goroutine 實際上跑在物理線程上的也就幾十個,但是java和c++創建成千上萬個線程會使得系統反應更慢,這是為什么goroutine能很快的原因。
那么goroutine協程之間是如何進行通信的呢?有兩種方式,
第一使用全局變量和鎖同步:讀寫鎖或互斥鎖對全局變量進行加鎖,實現多個goroute的數據共享。
第二:Channel 管道進行數據同步
互斥鎖就是將公共資源進行加鎖操作,以便于goroute對數據進行更改。
package main import ( "fmt" lock "sync" "time" ) type task struct { n int } //通過全局的 map 來通訊 var ( sum ) func calc(t *task) { var sum uint64 sum = 1 for i := 1; i < t.n; i++ { sum *= uint64(i) } fmt.Printf("%d! = %v\n", t.n, sum) lock.Lock() sum++ lock.Unlock() } func main() { for i := 0; i < 100; i++ { var t *task = &task{n: i} go calc(t) } time.Sleep(5 * time.Second) lock.Lock() // for k, v := range m { // fmt.Printf("%d! = %v\n", k, v) // } lock.Unlock() }
單純地將函數并發執行是沒有意義的。函數與函數間需要交換數據才能體現并發執行函數的意義。雖然可以使用共享內存進行數據交換,但是共享內存在不同的 goroutine 中容易發生競態問題。為了保證數據交換的正確性,必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。
Go 語言提倡使用通信的方法代替共享內存,這里通信的方法就是使用通道(channel)
channel 具有幾個特性:
1.類似unix中的管道(pipe)
2.先進先出
3.線程安全,多個goroutine同時訪問,不需要加鎖
4.channel是有類型的,一個整數的channel 只能存放整
2.1使用通道發送數據
通道創建后,就可以使用通道進行發送和接收操作。
1) 通道發送數據的格式
通道的發送使用特殊的操作符<-,將數據通過通道發送的格式為:
通道變量 <- 值
通道變量:通過make創建好的通道實例。
值:可以是變量、常量、表達式或者函數返回值等。值的類型必須與ch通道的元素類型一致。
2) 通過通道發送數據的例子
使用 make 創建一個通道后,就可以使用<-向通道發送數據,代碼如下:
// 創建一個空接口通道 ch := make(chan interface{}) // 將0放入通道中 ch <- 0 // 將hello字符串放入通道中 ch <- "hello"
2.2 使用通道接收數據
1)通道接收同樣使用<-操作符,通道接收有如下特性:
① 通道的收發操作在不同的兩個 goroutine 間進行。
由于通道的數據在沒有接收方處理時,數據發送方會持續阻塞,因此通道的接收必定在另外一個 goroutine 中進行。
② 接收將持續阻塞直到發送方發送數據。
如果接收方接收時,通道中沒有發送方發送數據,接收方也會發生阻塞,直到發送方發送數據為止
③ 每次接收一個元素。
通道一次只能接收一個數據元素。
通道的數據接收一共有以下 4 種寫法。
2) 阻塞接收數據
阻塞模式接收數據時,將接收變量作為<-操作符的左值,格式如下:
data := <-ch
執行該語句時將會阻塞,直到接收到數據并賦值給 data 變量。
3) 非阻塞接收數據
使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的數據。未接收到數據時,data 為通道類型的零值。
ok:表示是否接收到數據。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實現接收超時檢測,可以配合 select 和計時器 channel 進行,可以參見后面的內容。
4) 接收任意數據,忽略接收的數據
阻塞接收數據后,忽略從通道返回的數據,格式如下:
<-ch
執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略。這個方式實際上只是通過通道在 goroutine 間阻塞收發實現并發
2.3 發生阻塞的2種情況
1)發送方發送阻塞:在通道數據沒有接收方處理時,通道的數據一開始會存放到固定的數據緩沖區內,超出緩沖區的大小將發生持續阻塞。
package main func main() { var ch chan int ch = make(chan int, 5) //定義數據緩存區設置為5個大小 //將數據保存在緩沖區內并不會發生當前線程阻塞 for i := 0; i < 5; i++ { ch <- i } //但將第6個加入通道(超出緩沖區)就會立即阻塞當前的協程(即main線程) 最后panic ch <- 6 }
這個程序的執行結果直接painc 因為在管道加入ch <- 6 的時候因為緩存區沒有那么大,并且沒有接收方去消化數據,故painc。
2) 數據接收方發生阻塞:如果接收方沒有接收到數據,接收方等待發送方發送數據,等待的過程也會使數據接收的協程發生阻塞。
package main import ( "fmt" "time" ) func main() { var ch chan int ch = make(chan int) //無定義數據緩存區 go func() { var a = <-ch //執行第一次取出 fmt.Println(a) }() time.Sleep(time.Second * 4) //主線程等待4才給管道數據 ch <- 1 //通道里只入一個數據 //接收方協程是一個并發匿名函數 time.Sleep(time.Second * 5) //主線程等待5秒讓goroute有處理時間然后結束 }
這個程序的執行結果是延時4秒后控制臺打印出通道的值1,5秒后主程序結束。上邊的程序是先讓接收者協程開啟等待接收通道的值,而發送者是主函數延遲4秒后才將值放入通道ch,匿名函數中不得不等待發送者的值,所以造成了匿名并發函數的阻塞。 我們可以思考到,如果去掉4秒等待的時間, 這個程序就是使用channel作為協程之間同步的最簡單的例子,我們發現channel同步的特性就是無數據緩存區。
同樣一個程序,當你把接受者 go func() 程序放到 ch<-1 的下邊,就會painc。 為什么? 以為ch通道并沒有緩存區,并且接受者還未執行。導致painc。
package main import ( "fmt" "time" ) func main() { var ch chan int ch = make(chan int) //無定義數據緩存區 //time.Sleep(time.Second * 4) //主線程等待4才給管道數據 ch <- 1 //通道里只入一個數據 //接收方協程是一個并發匿名函數 //一個并發執行的協程 go func() { var a = <-ch //執行第一次取出 fmt.Println(a) }() time.Sleep(time.Second * 5) //主線程等待5秒讓goroute有處理時間然后結束 }
關于如何在go中利用協程對返回值進行處理就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。