1. 程式人生 > 程式設計 >go 協程返回值處理操作

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 管道進行資料同步

1.加鎖操作

互斥鎖就是將公共資源進行加鎖操作,以便於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()
}

2.channel管道通訊

單純地將函式併發執行是沒有意義的。函式與函式間需要交換資料才能體現併發執行函式的意義。雖然可以使用共享記憶體進行資料交換,但是共享記憶體在不同的 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有處理時間然後結束
}

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。