併發concurrency
簡單示例
- 任何一個函數在調用前加
go
便可以實現併發 - 注意如果不加任何通信,由於
go
所執行的協程並不會阻塞主函數的執行,主函數會同時繼續向後執行,直到結束時退出。
func Hello(name string) {
fmt.Println("Hello", name)
}
func main() {
//一般情況下這段代碼不會有任何輸出,因爲在調用輸出函數之前main函數已經結束了
go Hello("Bob")
}
Channel
Goroutine 奉行通過通信來共享內存,而不是共享內存來通信。
創建channel
c := make(chan int)
使用channel
c <- 1 //向c中寫入一個數字1
tmp := <-c //從c中讀出一個數字並存到一個新變量tmp中
- channel是引用類型,傳遞參數傳的是引用
- 讀/寫 無緩存 channel會造成阻塞
關閉channel
close(c)
示例改進
func Hello(c chan bool, name string) {
fmt.Println("Hello", name)
c <- true
}
func main() {
c := make(chan bool)
go Hello(c, "Bob")
<-c
}
遍歷channel
- 遍歷channel一定要注意能正確關閉channel,否則可能會發生死鎖
for v := range c {
fmt.Println(v)
}
有緩存channel
- 區別
- 無緩存channel讀寫之間是同步的,是相互阻塞的,寫入channel數據的操作會等待取出channel數據運行完之後才結束
- 有緩存channel讀寫是異步的,即寫入操作只要成功寫入後就結束,不管讀取操作成功執行與否
c := make(chan int) //無緩存channel
d := make(chan int, 1) //有緩存channel,緩存容量爲1
以下代碼運行時將沒有輸出
func Hello(c chan bool, name string) {
fmt.Println("Hello", name)
<-c
}
func main() {
//此處如果去掉1或者把1改爲0,就會變成同步操作
c := make(chan bool, 1)
go Hello(c, "Bob")
//異步通信,數據寫入完就結束,不管其他協程是否取出數據
c <- true
}
一個多協程通信的例子
func Counter(c chan bool, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
c <- true
}
func main() {
t1 := time.Now()
//設置使用的CPU核心數
//runtime.GOMAXPROCS(runtime.NumCPU())
//c := make(chan bool, 10) //有緩存
c := make(chan bool) //無緩存
for i := 0; i < 10; i++ {
go Counter(c, i)
}
for i := 0; i < 10; i++ {
<-c
}
t2 := time.Now()
fmt.Println(t2.Sub(t1))
}
- 雖然一些教材上說
goruntime
默認運行在一個CPU核心上,但是考慮到教材內容無法與時俱進,經過自己嘗試後發現是否添加runtime.GOMAXPROCS(runtime.NumCPU())
程序運行時間差異不大,而將參數設爲1後會明顯變慢,故猜測當前版本默認肯定不是單核心。具體解釋需要在官方文檔找到答案纔好(原諒本菜鳥不知怎麼找官方解釋……)
Select
- 類似於
switch
語句,但case
都是channel的相關操作,用來監聽和channel有關的IO操作,當 IO 操作發生時,觸發相應的動作。 - 可處理一個或多個 channel 的發送與接收
- 同時有多個可用的 channel時按隨機順序處理
- 可用空的 select 來阻塞 main 函數
- 可設置超時
func main() {
c1, c2 := make(chan int), make(chan string)
o := make(chan bool)
go func() {
for {
select {
case v, ok := <-c1:
if ok == false {
o <- true
break
}
fmt.Println("c1", v)
case v, ok := <-c2:
if ok == false {
o <- true
break
}
fmt.Println("c2", v)
}
}
}()
c1 <- 1
c2 <- "Hello"
c1 <- 2
c2 <- "Test"
close(c1)
close(c2)
<-o
}
//一個用select隨機生成01串的例子
func main() {
c := make(chan int)
go func() {
for v := range c {
fmt.Print(v)
}
}()
for i := 0; i < 10; i++ {
select {
case c <- 0:
case c <- 1:
}
}
close(c)
}
//在select中判斷超時
func main() {
c := make(chan int)
select {
case v := <-c:
fmt.Println(v)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
}
close(c)
}
同步
*需要導入sync包
sync.WaitGroup
- 可以用其創建任務組
wg := sync.WaitGroup{} //創建一個任務組
wg.add(10) //增加10個任務
wg.Done() //完成一個任務
wg.Wait() //等待任務執行結束
- 由於是值拷貝,所以需要傳入指針
一個多協程同步的例子
func Counter(wg *sync.WaitGroup, index int) {
a := 1
for i := 0; i < 100000000; i++ {
a += i
}
fmt.Println(index, a)
wg.Done()
}
func main() {
t1 := time.Now()
//runtime.GOMAXPROCS(runtime.NumCPU())
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go Counter(&wg, i)
}
wg.Wait()
t2 := time.Now()
fmt.Println(t2.Sub(t1))
}