首先我們來看線程,在golang里面也叫goroutine
在讀這篇文章之前,我們需要了解一下並發與並行。golang的線程是一種並發機制,而不是並行。它們之間的區別大家可以上網搜一下,網上有很多的介紹。
下面我們先來看一個例子吧
import(
"fmt"
)
funcmain(){
go fmt.Println("1")
fmt.Println("2")
}
在golang里面,使用go這個關鍵字,后面再跟上一個函數就可以創建一個線程。后面的這個函數可以是已經寫好的函數,也可以是一個匿名函數
funcmain(){
vari=3
go func(a int) {
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2")
}
上面的代碼就創建了一個匿名函數,並且還傳入了一個參數i,下面括號里的i是實參,a是形參。
那么上面的代碼能按照我們預想的打印1、2、3嗎?告訴你們吧,不能,程序只能打印出2。下面我把正確的代碼貼出來吧
import(
"fmt"
"time"
)
funcmain(){
var i = 3
go func(a int) {
fmt.Println(a)
fmt.Println("1")
}(i)
fmt.Println("2") time.Sleep(1 * time.Second)
}
我只是在最后加了一行讓主線程休眠一秒的代碼,程序就會依
次打印出2、3、1。
那為什么會這樣呢?因為程序會優先執行主線程,主線程執行完成后,程序會立即退出,沒有多余的時間去執行子線程。如果在程序的最后讓主線程休眠1秒鍾,那程序就會有足夠的時間去執行子線程。
線程先講到這里,下面我們來看看通道吧。
通道又叫channel,顧名思義,channel的作用就是在多線程之間傳遞數據的。
創建無緩沖channel
chreadandwrite :=make(chan int)
chonlyread := make(<-chan int) //創建只讀channel
chonlywrite := make(chan<- int) //創建只寫channel
下面我們來看一個例子:
ch :=make(chan int)
ch <- 1
go func() {
<-ch
fmt.Println("1")
}()
fmt.Println("2")
這段代碼執行時會出現一個錯誤:fatal error: all goroutines are asleep - deadlock!
這個錯誤的意思是說線程陷入了死鎖,程序無法繼續往下執行。那么造成這種錯誤的原因是什么呢?
我們創建了一個無緩沖的channel,然后給這個channel賦值了,程序就是在賦值完成后陷入了死鎖。因為我們的channel是無緩沖的,即同步的,賦值完成后來不及讀取channel,程序就已經阻塞了。這里介紹一個非常重要的概念:channel的機制是先進先出,如果你給channel賦值了,那么必須要讀取它的值,不然就會造成阻塞,當然這個只對無緩沖的channel有效。對於有緩沖的channel,發送方會一直阻塞直到數據被拷貝到緩沖區;如果緩沖區已滿,則發送方只能在接收方取走數據后才能從阻塞狀態恢復。
對於上面的例子有兩種解決方案:
1、給channel增加緩沖區,然后在程序的最后讓主線程休眠一秒,代碼如下:
ch :=make(chan int,1)
ch <- 1
go func() {
v := <-ch
fmt.Println(v)
}()
time.Sleep(1 * time.Second)
fmt.Println("2")
這樣的話程序就會依次打印出1、2
2、把ch<-1這一行代碼放到子線程代碼的后面,代碼如下:
ch :=make(chan int)
go func() {
v := <-ch
fmt.Println(v)
}()
ch <- 1
fmt.Println("2")
這里就不用讓主線程休眠了,因為channel在主線程中被賦值后,主線程就會阻塞,直到channel的值在子線程中被取出。
最后我們看一個生產者和消費者的例子:
import (
"fmt"
"time"
)
func produce(p chan<- int) {
for i := 0; i < 10; i++ {
p <- i
fmt.Println("send:", i)
}
}
func consumer(c <-chan int) {
for i := 0; i < 10; i++ {
v := <-c
fmt.Println("receive:", v)
}
}
func main() {
ch := make(chan int)
go produce(ch)
go consumer(ch)
time.Sleep(1 * time.Second)
}
在這段代碼中,因為channel是沒有緩沖的,所以當生產者給channel賦值后,生產者這個線程會阻塞,直到消費者線程將channel中的數據取出。消費者第一次將數據取出后,進行下一次循環時,消費者的線程也會阻塞,因為生產者還沒有將數據存入,這時程序會去執行生產者的線程。程序就這樣在消費者和生產者兩個線程間不斷切換,直到循環結束。
下面我們再看一個帶緩沖的例子:
import (
"fmt"
"time"
)
func produce(p chan<- int) {
for i := 0; i < 10; i++ {
p <- i
fmt.Println("send:", i)
}
}
func consumer(c <-chan int) {
for i := 0; i < 10; i++ {
v := <-c
fmt.Println("receive:", v)
}
}
func main() {
ch := make(chan int, 10)
go produce(ch)
go consumer(ch)
time.Sleep(1 * time.Second)
}
在這個程序中,緩沖區可以存儲10個int類型的整數,在執行生產者線程的時候,線程就不會阻塞,一次性將10個整數存入channel,在讀取的時候,也是一次性讀取。