您好,登錄后才能下訂單哦!
這篇文章主要介紹了Go并發的方法有哪些的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Go并發的方法有哪些文章都會有所收獲,下面我們一起來看看吧。
Golang 在語言層面對并發編程進行了支持,使用了一種協程(goroutine)機制,
協程本質上是一種用戶態線程,不需要操作系統來進行搶占式調度,但是又寄生于線程中,因此系統開銷極小,可以有效的提高線程的任務并發性,而避免多線程的缺點。但是協程需要語言上的支持,需要用戶自己實現調度器,因為在Go語言中,實現了調度器所以我們可以很方便的能過 go
關鍵字來使用協程。
func main() { for i := 0; i <10; i++ { go func(i int) { for { fmt.Printf("Hello goroutine %d\n",i) } }(i) } time.Sleep(time.Millisecond) }
最簡單的一個并發編程小例子,并發輸出一段話。
我們同時開了10個協程進行輸出,每次在fmt.printf
時交出控制權(不一定每次都會交出控制權),回到調度器中,再由調度器分配。
I/O,Select
channel
等待鎖
函數調用
runtime.Gosched()
我們看一個小例子:
func main() { var a [10]int for i := 0; i <10; i++ { go func(i int) { for { a[i]++ } }(i) } time.Sleep(time.Millisecond) fmt.Println(a) }
在這里,代碼直接鎖死,程序沒有退出,因為在執行函數中沒有協程的切換,因為 main
函數也是一個協程。
如果想要程序退出,可以通過 runtime.Gosched()
函數,在執行函數中添加一行。
for { a[i]++ runtime.Gosched() }
加上這個函數之后,代碼是可以正常執行了,但是真的是正常執行嗎?不一定,我們可以使用 -reac
命令來看一下數據是否有沖突:
這說明數據還是有沖突的,數組a
中的元素一邊在做自增,一邊在輸出。解決這個問題,我們只能使用 channel 來解決。
Channel 中 Go語言在語言級別提供了對 goroutine 之間通信的支持,我們可以使用 channel 在兩個或者多個goroutine之間進行信息傳遞,能過 channel 傳遞對像的過程和調用函數時的參數傳遞行為一樣,可以傳遞普通參數和指針。
Channel 有兩種模式:
var ch2 = make(chan int) // 無緩沖 channel,同步 var ch3 = make(chan int, 2) // 有緩沖 channel, 異步
無緩沖的方式,數據進入 channel 只要沒有被接收,就會處在阻塞狀態。
var ch2 = make(chan int) // 無緩沖 channel,同步 ch2 <- 1 ch2 <- 2 // error: all goroutines are asleep - deadlock! fmt.Println(<-ch2)
如果想要運行,必須要再開一個協程不停的去請求數據:
var ch2 = make(chan int) // 無緩沖 channel,同步 go func() { for { n := <-ch2 fmt.Println(n) } }() ch2 <- 1 ch2 <- 2
有緩沖的方式,只要緩沖區沒有滿就可以一直進數據,緩沖區在填滿之后沒有接收也會處理阻塞狀態。
func bufferChannel() { var ch3 = make(chan int,2) ch3<-1 ch3<-2 fmt.Println(ch3) // 不加這一行的話,是可以正常運行的 ch3<-3 // error: all goroutines are asleep - deadlock! }
比如我現在有一個函數創建一個 channel,并且不斷的需要消費channel中的數據:
func worker(ch chan int) { for { fmt.Printf("hello goroutine worker %d\n", <-ch) } } func createWorker() chan int{ ch := make(chan int) go worker(ch) return ch } func main() { ch := createWorker() ch<-1 ch<-2 ch<-3 time.Sleep(time.Millisecond) }
這個函數我是要給別人用的,但是我怎么保證使用 createWorker 函數創建的 channel 都是往里面傳入數據的呢?
如果外面有人消費了這個 channel 中的數據,我們怎么限制?
這個時候,我們就可以給返回的channel 加上方向,指明這個 channel 中能往里傳入數據,不能從中取數據:
func worker(ch <-chan int) { for { fmt.Printf("hello goroutine worker %d\n", <-ch) } } func createWorker() chan<- int{ ch := make(chan int) go worker(ch) return ch }
我們可以在返回 channel 的地方加上方向,指明返回的函數只能是一個往里傳入數據,不能從中取數據。
并且我們還可以給專門消費的函數加上一個方向,指明這個函數只能出不能進。
在使用 channel 的時候,隨說我們可以等待channel中的函數使用完之后自己結束,或者等待 main 函數結束時關閉所有的 goroutine 函數,但是這樣的方式顯示不夠優雅。
當一個數據我們明確知道他的結束時候,我們可以發送一個關閉信息給這個 channel ,當這個 channel 接收到這個信號之后,自己關閉。
// 方法一 func worker(ch <-chan int) { for { if c ,ok := <- ch;ok{ fmt.Printf("hello goroutine worker %d\n", c) }else { break } } } // 方法二 func worker(ch <-chan int) { for c := range ch{ fmt.Printf("hello goroutine worker %d\n", c) } } func main() { ch := createWorker() ch<-1 ch<-2 ch<-3 close(ch) time.Sleep(time.Millisecond) }
通過 Close
b函數,我們可以能過 channel 已經關閉,并且我們還可以通過兩種方法判斷通道內是否還有值。
當我們在實際開發中,我們一般同時處理兩個或者多個 channel 的數據,我們想要完成一個那個 channel 先來數據,我們先來處理個那 channel 怎么辦呢?
此時,我們就可以使用 select 調度:
func genInt() chan int { ch := make(chan int) go func() { i := 0 for { // 隨機兩秒以內生成一次數據 time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) ch <- i i++ } }() return ch } func main() { var c1 = genInt() var c2 = genInt() for { select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <- c2: fmt.Printf("server 2 generator %d\n", n) } } }
for { tick := time.Tick(time.Second) select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <-c2: fmt.Printf("server 2 generator %d\n", n) case <-tick: fmt.Println("定時每秒輸出一次!") } }
for { tick := time.Tick(time.Second) select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <-c2: fmt.Printf("server 2 generator %d\n", n) case <-tick: fmt.Println("定時每秒輸出一次!") case <-time.After(1300 * time.Millisecond): // 如果 1.3秒內沒有數據進來,那么就輸出超時 fmt.Println("timeout") } }
type atomicInt struct { value int lock sync.Mutex } func (a *atomicInt) increment() { a.lock.Lock() defer a.lock.Unlock() // 使用 defer 解鎖,以防忘記 a.value++ } func main() { var a atomicInt a.increment() go func() { a.increment() }() time.Sleep(time.Millisecond) fmt.Println(a.value) }
type waitGrouInt struct { value int wg sync.WaitGroup } func (w *waitGrouInt) addInt() { w.wg.Add(1) w.value++ } func main() { var w waitGrouInt for i := 0; i < 10; i++ { w.addInt() w.wg.Done() } w.wg.Wait() fmt.Println(w.value) }
關于“Go并發的方法有哪些”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Go并發的方法有哪些”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。