Golang協程,通道詳解

進程、線程以及并行、并發

關于進程和線程

進程(Process)就是程序在操作系統中的一次執行過程,是系統進行資源分配和調度的基
本單位,進程是一個動態概念,是程序在執行過程中分配和管理資源的基本單位,每一個進
程都有一個自己的地址空間。
一個進程至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。
通俗的講進程就是一個正在執行的程序。
線程 是進程的一個執行實例,是程序執行的最小單元,它是比進程更小的能獨立運行的基
本單位;
一個進程可以創建多個線程,同一個進程中的多個線程可以并發執行,一個程序要運行的話
至少有一個進程。

關于并行和并發

并發:多個線程同時競爭一個位置,競爭到的才可以執行,每一個時間段只有一個線程在執
行。
并行:多個線程可以同時執行,每一個時間段,可以有多個線程同時執行。
總結而言:
多線程程序在單核 CPU 上面運行就是并發
多線程程序在多核 CUP 上運行就是并行。
如果線程數大于 CPU 核數,則多線程程序在多個 CPU 上面運行既有并行又有并發;

?Golang 中的協程(goroutine)以及主線程

golang 中的主線程:(可以理解為線程/也可以理解為進程),在一個 Golang 程序的主線程
上可以起多個協程
Golang 中多協程可以實現并行或者并發。
協程:可以理解為用戶級線程,這是 對內核透明的,也就是系統并不知道有協程的存在 ,是
完全由用戶自己的程序進行調度的。Golang 的一大特色就是從語言層面原生支持協程,在
函數或者方法前面加 go 關鍵字就可創建一個協程。可以說 Golang 中的協程就是goroutine 。
Golang 中的多協程有點類似其他語言中的多線程。
多協程和多線程:Golang 中每個 goroutine (協程) 默認占用內存遠比 Java 、C 的線程少。
OS 線程(操作系統線程)一般都有固定的棧內存(通常為 2MB 左右),一個 goroutine (協
程) 占用內存非常小,只有 2KB 左右,多協程 goroutine 切換調度開銷方面遠比線程要少。
這也是為什么越來越多的大公司使用 Golang 的原因之一。

Goroutine 的使用

案例如下:
package mainimport("fmt""time"
)// 在主線程中也每隔10毫輸出"衛宮士郎", 輸出2次后,退出程序
// 要求主線程和goroutine同時執行
func test() {for i := 0; i < 10; i++ {fmt.Println("test() 測試專用..........")time.Sleep(time.Millisecond * 100)}
}func main(){go test()for i := 1; i <=2; i++ {fmt.Println("main () 衛宮士郎")time.Sleep(time.Millisecond*10)}}

暴露出一個問題:主線程執行完畢后即使協程沒有執行完畢

所以我們對代碼進行改造,可以讓主線程和協程并行的同時,主線程執行完畢還不會同時帶領協程退出運行。

?注意:
1、主線程執行完畢后即使協程沒有執行完畢程序也會退出

2、協程可以在主線程沒有執行完畢前提前退出協程是否執行完畢不會影響主線程的執行為了保證我們的程序可以順利執行我們想讓協程執行完畢后在執行主進程退出。

這個時候我們可以使用sync.WaitGroup 等待協程執行完畢

?sync.WaitGroup

sync.WaitGroup 可以實現主線程等待協程執行完畢。
package mainimport("fmt""time""sync"
)// 在主線程中也每隔10毫輸出"衛宮士郎", 輸出2次后,退出程序
// 要求主線程和goroutine同時執行
//主線程退出后所有的協程無論有沒有執行完畢都會退出,所以我們在主進程中可以通過WaitGroup等待協程執行完畢
var sw sync.WaitGroupfunc test() {for i := 0; i < 10; i++ {fmt.Println("test() 測試專用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //協程計數器-1
}func main(){sw.Add(1) //協程計數器+1go test()//表示開啟一個協程for i := 1; i <=2; i++ {fmt.Println("main () 衛宮士郎")time.Sleep(time.Millisecond*10)}sw.Wait() //等待協程執行完畢...fmt.Println("主線程執行完畢、、、、、、")
}

啟動多個 Goroutine?

package mainimport("fmt""time""sync"
)// 多個協程Goroutine啟動var sw sync.WaitGroupfunc test0() {for i := 0; i < 5; i++ {fmt.Println("test0() 測試專用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //協程計數器-1
}func test1() {for i := 0; i < 5; i++ {fmt.Println("test1() 測試專用..........")time.Sleep(time.Millisecond * 100)}sw.Done() //協程計數器-1
}func main(){sw.Add(1) //協程計數器+1go test0()//表示開啟一個協程sw.Add(1)//協程計數器+1go test1()//表示開啟一個協程for i := 1; i <=2; i++ {fmt.Println("main () 衛宮士郎")time.Sleep(time.Millisecond*10)}sw.Wait() //等待協程執行完畢...fmt.Println("主線程執行完畢、、、、、、")
}

多次執行上面的代碼,會發現每次打印的數字的順序都不一致。這是因為 10 個 goroutine
是并發執行的,而 goroutine 的調度是隨機的。

設置 Golang 并行運行的時候占用的 cup 數量

Go 運行時的調度器使用 GOMAXPROCS 參數來確定需要使用多少個 OS 線程來同時執行 Go
代碼。默認值是機器上的 CPU 核心數。例如在一個 8 核心的機器上,調度器會把 Go 代碼同
時調度到 8 OS 線程上。
Go 語言中可以通過 runtime.GOMAXPROCS() 函數設置當前程序并發時占用的 CPU 邏輯核心
數。
Go1.5 版本之前,默認使用的是單核心執行。 Go1.5 版本之后,默認使用全部的 CPU 邏輯核
心數。
package mainimport ("fmt""runtime"
)func main() {//獲取當前計算機上面的Cup個數cpuNum := runtime.NumCPU()fmt.Println("cpuNum=", cpuNum)//可以自己設置使用多個cpuruntime.GOMAXPROCS(cpuNum - 1)fmt.Println("設置完成")
}//cpuNum= 8
//設置完成

來求一個素數的操作如下:

package mainimport ("fmt""time"
)func main() {start := time.Now().Unix()fmt.Println(start)for num := 2; num < 10; num++ {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if  flag {fmt.Println(num, "是素數")}}end := time.Now().Unix()fmt.Println(end)fmt.Println(end-start) }

goroutine ?for循環實現

package mainimport ("fmt""sync""time"
)//需求:要統計1-120000的數字中那些是素數?goroutine  for循環實現/*
1 協程  統計  1-300002 協程  統計  30001-600003 協程  統計  60001-900004 協程  統計  90001-120000// start:(n-1)*30000+1       end:n*30000
*/
var wg sync.WaitGroupfunc test(n int) {for num := (n-1)*30000 + 1; num < n*30000; num++ {if num > 1 {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {// fmt.Println(num, "是素數")}}}wg.Done()
}func main() {for i := 1; i <= 4; i++ {wg.Add(1)go test(i)}wg.Wait()fmt.Println("執行完畢")}

Channel 管道

channel

單純地將函數并發執行是沒有意義的。

函數與函數間需要交換數據才能體現并發執行函數的意義。

雖然可以使用共享內存進行數據交換,但是共享內存在不同的goroutine中容易發生競態問題。為了保證數據交換的正確性,必須使用互斥量對內存進行加鎖,這種做法勢必造成性能問題。

Go語言的并發模型是CSP(Communicating Sequential Processes),提倡通過通信共享內存而不是通過共享內存而實現通信。

如果說goroutine是Go程序并發的執行體,channel就是它們之間的連接。

channel是可以讓一個goroutine發送特定值到另一個goroutine的通信機制。

Go 語言中的通道(channel)是一種特殊的類型。通道像一個傳送帶或者隊列,總是遵循先入先出(First In First Out)的規則,保證收發數據的順序。每一個通道都是一個具體類型的導管,也就是聲明channel的時候需要為其指定元素類型。

channel類型

channel是一種類型,一種引用類型。聲明通道類型的格式如下:

    var 變量 chan 元素類型  

舉幾個例子:

    var ch1 chan int   // 聲明一個傳遞整型的通道var ch2 chan bool  // 聲明一個傳遞布爾型的通道var ch3 chan []int // 聲明一個傳遞int切片的通道    

創建channel

通道是引用類型,通道類型的空值是nil。

var ch chan int
fmt.Println(ch) // <nil>
package mainimport "fmt"func main() {ch1 := make(chan int ,4)ch1<- 1ch1<- 2ch1<- 3ch2 := ch1ch2<-4<-ch1<-ch1<-ch1d:= <-ch1fmt.Println(d)
}//4

副本ch2的值添加后,取出ch1的值改變了

聲明的通道后需要使用make函數初始化之后才能使用。

創建channel的格式如下:

    make(chan 元素類型, [緩沖大小])   

channel的緩沖大小是可選的。

舉幾個例子:

//創建一個能存儲 10 個 int 類型數據的管道
ch1 := make(chan int, 10)
//創建一個能存儲 4 個 bool 類型數據的管道
ch2 := make(chan bool, 4)
//創建一個能存儲 3 個[]int 切片類型數據的管道
ch3 := make(chan []int, 3)
package mainimport "fmt"func main() {//創建channelch := make(chan int, 3)//2、給管道里面存儲數據ch <- 12ch <- 33ch <- 3234//獲取管道里面的內容a := <-chfmt.Println(a) //12<-ch //從管道里面取值   //33c := <-chfmt.Println(c) //3234ch <- 1ch <- 22//打印管道的長度和容量fmt.Printf("值:%v 容量:%v 長度%v\n", ch, cap(ch), len(ch)) 
}

已經消費了的,就相當于沒有,再添加的從新算?

channel操作

通道有發送(send)、接收(receive)和關閉(close)三種操作。

發送和接收都使用<-符號。

現在我們先使用以下語句定義一個通道:

ch := make(chan int)    

發送

將一個值發送到通道中。

ch <- 10 // 把10發送到ch中   

接收

從一個通道中接收值。

x := <- ch // 從ch中接收值并賦值給變量x
<-ch       // 從ch中接收值,忽略結果   

關閉

我們通過調用內置的close函數來關閉通道。

    close(ch)   

?關于關閉通道需要注意的事情是,只有在通知接收方goroutine所有的數據都發送完畢的時候才需要關閉通道。通道是可以被垃圾回收機制回收的,它和關閉文件是不一樣的,在結束操作之后關閉文件是必須要做的,但關閉通道不是必須的。

關閉后的通道有以下特點:

    1.對一個關閉的通道再發送值就會導致panic。2.對一個關閉的通道進行接收會一直獲取值直到通道為空。3.對一個關閉的并且沒有值的通道執行接收操作會得到對應類型的零值。4.關閉一個已經關閉的通道會導致panic。  

管道阻塞

無緩沖的通道

如果創建管道的時候沒有指定容量,那么我們可以叫這個管道為無緩沖的管道
無緩沖的管道又稱為阻塞的管道。我們來看一下下面的代碼:
package main
import ("fmt"
)func main() {ch := make(chan int)ch <- 123fmt.Println("傳遞成功......")
}   

上面這段代碼能夠通過編譯,但是執行的時候會出現以下錯誤:

fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:8 +0x31
exit status 2

為什么會出現死鎖

因為我們使用ch := make(chan int)創建的是無緩沖的通道,無緩沖的通道只有在有接收值的時候才能發送值。(小區沒代收快遞點,需要快遞小哥直接送到手上)

上面的代碼會阻塞在ch <- i這一行代碼形成死鎖

因為我們使用ch := make(chan int)創建的是無緩沖的通道,無緩沖的通道只有在有人接收值的時候才能發送值。

上面的代碼會阻塞在ch <- 123這一行代碼形成死鎖,那如何解決這個問題呢?

一種方法是啟用一個goroutine去接收值,例如:

func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}
func main() {ch := make(chan int)go recv(ch) // 啟用goroutine從通道接收值ch <- 10fmt.Println("發送成功")
}   

無緩沖通道上的發送操作會阻塞,直到另一個goroutine在該通道上執行接收操作,這時值才能發送成功,兩個goroutine將繼續執行。

有緩沖的通道

解決上面問題的方法還有一種就是使用有緩沖區的通道。

package main
import ("fmt"
)// func recover(ch chan int){
// 	rec := <- ch
// 	fmt.Println("接收成功",rec)
// }func main() {ch := make(chan int,1)// go recover(ch)ch <- 123fmt.Println("傳遞成功......")
}   

只要通道的容量大于零,那么該通道就是有緩沖的通道,通道的容量表示通道中能存放元素的數量。(小區快遞格子就一個,你取走了,別人能再放)

?循環遍歷管道數據

循環的話,我們就會提到for,但是for有兩種循環形式

for range 和 for 用兩種方式來操作

for range循環遍歷管道的值 ?,注意:管道沒有key

package mainimport "fmt"func main() {ch1 := make(chan int,5)for i := 1; i <= 5; i++ {ch1 <- i}for v := range ch1 {fmt.Println(v)}}

我們發現雖然可以正常編譯,運行,但是會出現如下情況:

1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()E:/goroutine_channel_demo/route_demo/main.go:14 +0xb4
exit status 2

這樣也會產生死鎖,使用for range遍歷通道,當通道被關閉的時候就會退出for range,如果沒有關閉管道就會報錯fatal error: all goroutines are asleep - deadlock!

如果通過for range循環的方式來從管道取數據,在插入數據的時候一定要close()

package main
import ("fmt"
)func main() {var ch1 = make(chan int, 5)for i := 1; i <= 5; i++ {ch1 <- i}close(ch1) //關閉管道//for range循環遍歷管道的值  ,注意:管道沒有keyfor v := range ch1 {fmt.Println(v)}
}

通過內置的close()函數關閉channel(如果你的管道不往里存值或者取值的時候一定記得關閉管道)

?第二種方法

package main
import ("fmt"
)func main() {//通過for循環遍歷管道的時候管道可以不關閉var ch2 = make(chan int, 5)for i := 1; i <= 5; i++ {ch2 <- i}for j := 0; j < 5; j++ {fmt.Println(<-ch2)}
}

并發安全和鎖

有時候在Go代碼中可能會存在多個goroutine同時操作一個資源(臨界區),這種情況會發生競態問題(數據競態)。

互斥鎖

互斥鎖是傳統并發編程中對共享資源進行訪問控制的主要手段,它由標準庫 sync 中的 Mutex
結構體類型表示。 sync.Mutex 類型只有兩個公開的指針方法, Lock Unlock Lock 鎖定當
前的共享資源, Unlock 進行解鎖
package mainimport ("fmt""sync""time"
)var count = 0
var sw sync.WaitGroupvar mutex sync.Mutexfunc test() {mutex.Lock()count++fmt.Println("the count is : ", count)time.Sleep(time.Millisecond)mutex.Unlock()sw.Done()
}func main() {for r := 0; r < 20; r++ { //開啟20個協程來進行這個操作wg.Add(1)go test()}sw.Wait()}
使用互斥鎖能夠保證同一時間有且只有一個 goroutine 進入臨界區,其他的 goroutine 則在等
待鎖;當互斥鎖釋放后,等待的 goroutine 才可以獲取鎖進入臨界區,多個 goroutine 同時等
待一個鎖時,喚醒的策略是隨機的。
雖然使用互斥鎖能解決資源爭奪問題,但是并不完美,通過全局變量加鎖同步來實現通訊,
并不利于多個協程對全局變量的讀寫操作。這個時候我們也可以通過另一種方式來實現上面
的功能管道(Channel)

讀寫互斥鎖

互斥鎖是完全互斥的,但是有很多實際的場景下是讀多寫少的當我們并發的去讀取一個資源不涉及資源修改的時候是沒有必要加鎖的,這種場景下使用讀寫鎖是更好的一種選擇。

讀寫鎖在Go語言中使用sync包中的RWMutex類型。

讀寫鎖分為兩種:讀鎖和寫鎖。當一個goroutine獲取讀鎖之后,其他的goroutine如果是獲取讀鎖會繼續獲得鎖,如果是獲取寫鎖就會等待;

當一個goroutine獲取寫鎖之后,其他的goroutine無論是獲取讀鎖還是寫鎖都會等待。

package mainimport("fmt""sync""time"
)var (x      int64wg     sync.WaitGrouplock   sync.Mutexrwlock sync.RWMutex
)func write() {// lock.Lock()   // 加互斥鎖rwlock.Lock() // 加寫鎖x = x + 1time.Sleep(10 * time.Millisecond) // 假設讀操作耗時10毫秒fmt.Println("=========進行寫操作")rwlock.Unlock()                   // 解寫鎖// lock.Unlock()                     // 解互斥鎖wg.Done()
}func read() {// lock.Lock()                  // 加互斥鎖rwlock.RLock()               // 加讀鎖time.Sleep(time.Millisecond) // 假設讀操作耗時1毫秒fmt.Println("=========進行讀操作")rwlock.RUnlock()             // 解讀鎖// lock.Unlock()                // 解互斥鎖wg.Done()
}func main() {for i := 0; i < 3; i++ {wg.Add(1)go write()}for i := 0; i < 10; i++ {wg.Add(1)go read()}wg.Wait()}/**/
總結:
當我們對一個不會變化的數據只做“讀”操作的話,是不存在資源競爭的問題的。
因為數據是不變的,不管怎么讀取,多少 goroutine 同時讀取,都是可以的。
所以問題不是出在“讀”上,主要是修改,也就是“寫”。
修改的數據要同步,這樣其他goroutine 才可以感知到。
所以真正的互斥應該是讀取和修改、修改和修改之間,讀和讀是沒有互斥操作的必要的。
因此,衍生出另外一種鎖,叫做讀寫鎖
讀寫鎖可以讓 多個讀操作并發,同時讀取 ,但是 對于寫操作是完全互斥 的。
(也就是說,當一個 goroutine 進行寫操作的時候,其他 goroutine 既不能進行讀操作,也不能進行寫操作)

Goroutine 結合 Channel 管道

需求 1

定義兩個方法,一個方法給管道里面寫數據,一個給管道里面讀取數據。要求同步
進行。
1 、開啟一個 fn1 的的協程給向管道 inChan 中寫入 100 條數據
2 、開啟一個 fn2 的協程讀取 inChan 中寫入的數據
3 、注意: fn1 fn2 同時操作一個管道
4 、主線程必須等待操作完成后才可以退出

package mainimport ("fmt""sync""time"
)
//這是一個無緩存通道案例
//定義sync等待協程完畢
var wg sync.WaitGroupfunc fn1(intChan chan int) {for i := 0; i < 10; i++ {intChan <- i + 1fmt.Println("寫入數據=", i+1)time.Sleep(time.Millisecond * 100)}close(intChan)  //寫入操作完畢,關閉寫入的協程wg.Done()
}
func fn2(intChan chan int) {for v := range intChan {  //通道回顯只有一個值fmt.Printf("讀到數據=%v\n", v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan := make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println("讀取完畢...")
}

?需求 2

goroutine 結合 channel 實現統計 1-120000 的數字中那些是素數?

package mainimport("fmt""sync"
)var sw sync.WaitGroup
//向 intChan放入 1-120個數,創建協程
func putNum(intChan chan int ){for i := 0; i < 120; i++ {intChan <- i}close(intChan)sw.Done()
}// 從 intChan取出數據,并判斷是否為素數,如果是,就把得到的素數放在primeChanfunc primeNum(intChan chan int,primeChan chan int, exitChan chan bool ){for num := range intChan {var flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {primeChan <- num //num是素數}
}//要關閉 primeChan// close(primeChan) //如果一個channel關閉了就沒法給這個channel發送數據了//什么時候關閉primeChan?//給exitChan里面放入一條數據exitChan <- true sw.Done()}//printPrime打印素數的方法
func printPrime(primeChan chan int) {for v := range primeChan {fmt.Println(v)}sw.Done()
}func main(){intChan := make(chan int,1000) //在intchan中放入數字primeChan := make(chan int,1000) //從 intChan取出數據,判斷是否是素數exitChan := make(chan bool ,20) //標識primeChan close,內部數據滿足設定的緩存數量就關閉//存放數字的協程sw.Add(1)go putNum(intChan)//統計素數的協程for i := 0; i < 20; i++ {   //你要開啟幾個primechan的協程就寫幾個,對應的exitchan要一致sw.Add(1)go primeNum(intChan ,primeChan , exitChan )}//打印素數的協程sw.Add(1)go printPrime(primeChan)//判斷exitChan是否存滿值sw.Add(1)go func() {for i := 0; i < 20; i++ {<-exitChan}close(primeChan) //關閉primeChansw.Done()}()sw.Wait()fmt.Println("執行完畢....")}

單向管道

有的時候我們會將管道作為參數在多個任務函數間傳遞,很多時候我們在不同的任務函數中
使用管道都會對其進行限制,比如限制管道在函數中只能發送或只能接收。
案例如下:
package mainimport "fmt"//單向管道
func main() {// 1、在默認情況下下,管道是雙向ch := make(chan int, 2)ch <- 1ch <- 2a := <-chb := <-chfmt.Println(a, b) //1,2// 2、管道聲明為只寫ch1 := make(chan<- int, 2)ch1 <- 10ch1 <- 12// <-ch1   //receive from send-only type chan<- int// 3、管道聲明為只讀ch2 := make(<-chan int, 2)ch2 <- 3c := <-ch2fmt.Println(c) //.\main.go:25:2: invalid operation: cannot send to receive-only channel ch2 (variable of type <-chan int)}

修改之前的案例如下:

package mainimport ("fmt""sync""time"
)
//這是一個無緩存通道案例
//定義sync等待協程完畢
var wg sync.WaitGroupfunc fn1(intChan chan<- int) {for i := 0; i < 10; i++ {intChan <- i + 1fmt.Println("寫入數據=", i+1)time.Sleep(time.Millisecond * 100)}close(intChan)  //寫入操作完畢,關閉寫入的協程wg.Done()
}
func fn2(intChan <-chan int) {for v := range intChan {  //通道回顯只有一個值fmt.Printf("讀到數據=%v\n", v)time.Sleep(time.Millisecond * 50)}wg.Done()
}
func main() {allChan := make(chan int, 100)wg.Add(1)go fn1(allChan)wg.Add(1)go fn2(allChan)wg.Wait()fmt.Println("讀取完畢...")
}/*
寫入數據= 1
讀到數據=1
寫入數據= 2
讀到數據=2
寫入數據= 3
讀到數據=3
寫入數據= 4
讀到數據=4
寫入數據= 5
讀到數據=5
寫入數據= 6
讀到數據=6
寫入數據= 7
讀到數據=7
寫入數據= 8
讀到數據=8
寫入數據= 9
讀到數據=9
寫入數據= 10
讀到數據=10
讀取完畢...
*/

?select 多路復用

在某些場景下我們需要同時從多個通道接收數據,這個時候就可以用到golang中給我們提供的select多路復用

如果只想在main方法內進行,就可以用這個方法,其他的就是定義協程了

使用select來獲取channel里面的數據的時候不需要關閉channel

package mainimport("fmt""time"
)func main(){
// 在某些場景下我們需要同時從多個通道接收數據,這個時候就可以用到golang中給我們提供的select多路復用//如果只想在main方法內進行,就可以用這個方法,其他的就是定義協程了//1.定義一個管道 10個數據int
intoChan := make(chan int ,10)
for i := 0; i < 10; i++ {intoChan <- i
}
//2.定義一個管道 5個數據string
stringChan := make(chan string,5)
for i := 0; i < 5; i++ {stringChan <- "衛宮士郎" 
}
//定義一個for的無限循環
for{select{case value := <- intoChan:fmt.Printf("從 intChan 讀取的數據%d\n", value)case value := <-stringChan:fmt.Printf("從 stringChan 讀取的數據%v\n", value)time.Sleep(time.Millisecond * 50)default:fmt.Printf("數據獲取完畢")return //注意退出...}
}}/*
從 stringChan 讀取的數據衛宮士郎
從 stringChan 讀取的數據衛宮士郎
從 intChan 讀取的數據0
從 intChan 讀取的數據1
從 stringChan 讀取的數據衛宮士郎
從 intChan 讀取的數據2
從 intChan 讀取的數據3
從 stringChan 讀取的數據衛宮士郎
從 intChan 讀取的數據4
從 stringChan 讀取的數據衛宮士郎
從 intChan 讀取的數據5
從 intChan 讀取的數據6
從 intChan 讀取的數據7
從 intChan 讀取的數據8
從 intChan 讀取的數據9
數據獲取完畢*/
select 的使用類似于 switch 語句,它有一系列 case 分支和一個默認的分支。每個 case 會對
應一個管道的通信(接收或發送)過程。
select 會一直等待,直到某個 case 的通信操作完成 時,就會執行 case 分支對應的語句。

Goroutine Recover 解決協程中出現的 Panic

defer + recover

延遲執行(定義的func自執行函數出現問題就交給defer)

其他的協程還可以繼續進行

package mainimport ("fmt""time"
)//函數
func test0() {for i := 0; i < 10; i++ {time.Sleep(time.Millisecond * 50)fmt.Println("遠坂凜")}
}//函數
func test1() {//這里我們可以使用defer + recover //延遲執行(定義的func自執行函數出現問題就交給defer)//其他的協程還可以繼續進行defer func() {//捕獲test拋出的panicif err := recover(); err != nil {fmt.Println("test1() 發生錯誤", err)}}()//定義了一個mapvar myMap map[int]stringmyMap[0] = "golang" //error}func main() {go test0()go test1()//防止主進程退出這里使用time.Sleep演示,搭建也可以用sync.WaitGrouptime.Sleep(time.Second)
}

注意,調用recover()來捕獲 goroutine 恐慌只在一個defer函數內部有用;否則,該函數將返回nil并且沒有其他作用。這是因為defer函數也是在周圍函數恐慌時執行的。

在 Go 中,panic是一個停止普通流程的內置函數:

func main() {fmt.Println("a")panic("foo")fmt.Println("b")
}

該代碼打印a,然后在打印b之前停止:

a
panic: foogoroutine 1 [running]:
main.main()main.go:7 +0xb3

一旦恐慌被觸發,它將繼續在調用棧中向上運行,直到當前的 goroutine 返回或者panicrecover捕獲:

func main() {defer func() {                       // ?if r := recover(); r != nil {fmt.Println("recover", r)}}()f()                                  // ?
}func f() {fmt.Println("a")panic("foo")fmt.Println("b")
}

? 延遲閉包內調用recover

? 調用ff恐慌。這種恐慌被前面的recover所抓住。

f函數中,一旦panic被調用,就停止當前函數的執行,并向上調用棧:main。在main中,因為恐慌是由recover引起的,所以并不停止 goroutine:

a
recover foo

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/38252.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38252.shtml
英文地址,請注明出處:http://en.pswp.cn/news/38252.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【BASH】回顧與知識點梳理(二十三)

【BASH】回顧與知識點梳理 二十三 二十三. Linux 賬號管理&#xff08;二&#xff09;23.1 賬號管理新增與移除使用者&#xff1a; useradd, 相關配置文件, passwd, usermod, userdelusermoduserdel 23.2 用戶功能&#xff08;普通用戶可使用&#xff09;idfingerchfnchsh 23.3…

【數據庫系統】--【2】DBMS架構

DBMS架構 01DBMS架構概述02 DBMS的物理架構03 DBMS的運行和數據架構DBMS的運行架構DBMS的數據架構PostgreSQL的體系結構RMDB的運行架構 04DBMS的邏輯和開發架構DBMS的層次結構DBMS的開發架構DBMS的代碼架構 05小結 01DBMS架構概述 02 DBMS的物理架構 數據庫系統的體系結構 數據…

騰訊Perfdog支持Windows PC端體驗性能測試

一、背景 最近在做抖音的小玩法&#xff0c;其基于unity引擎&#xff0c;然后掛載到直播伴侶。以及Perfdog近期也支持了Windows的測試&#xff0c;所以做一個體驗測試。 二、如何做 查看PC端的支持&#xff0c;目前是beat版本 選擇或搜索自己需要的對應的程序&#xff0c;如…

python實現文本相似度排名計算

項目中&#xff0c;客戶突然提出需要根據一份企業名單查找對應的內部系統用戶信息&#xff0c;然后根據直接的企業社會統一信用號和企業名稱進行匹配&#xff0c;發現匹配率只有2.86%&#xff0c;低得可憐。所以根據客戶的要求&#xff0c;需要將匹配率提高到70-80%左右&#x…

vue2+百度地圖web端開發

在Vue 2中開發百度地圖Web端應用&#xff0c;你可以使用百度地圖JavaScript API來實現地圖功能。以下是一個簡單的示例&#xff1a; 簡單的示例&#xff1a; 首先&#xff0c;在你的Vue項目中安裝vue-baidu-map插件&#xff1a; npm install vue-baidu-map --save在你的Vue組…

大數據Flink(五十九):Flink on Yarn的三種部署方式介紹以及注意

文章目錄 Flink on Yarn的三種部署方式介紹以及注意 一、Pre-Job 模式部署作業

對任意類型數都可以排序的函數:qsort函數

之前我們學習過冒泡排序&#xff1a; int main() {int arr[] { 9,7,8,6,5,4,3,2,1,0 };int sz sizeof(arr)/sizeof(arr[0]);int i 0;for (i 0; i < sz-1; i) {int j 0;for (j 0; j < sz-1-i; j) {if (arr[j] > arr[j 1]){int temp 0;temp arr[j];arr[j] ar…

接口測試及接口抓包常用的測試工具

接口 接口測試是測試系統組件間接口的一種測試。接口測試主要用于檢測外部系統與系統之間以及內部各個子系統之間的交互點。測試的重點是要檢查數據的交換&#xff0c;傳遞和控制管理過程&#xff0c;以及系統間的相互邏輯依賴關系等。 接口測試的重要性 是節省時間前后端不…

七、dokcer-compose部署springboot的jar

1、準備 打包后包名為 ruoyi-admin.jar 增加接口 httpL//{ip}:{port}/common/test/han #環境變量預application.yml 中REDIS_HOSTt的值&#xff0c;去環境變量去找&#xff1b;如果找不到REDIS_HOST就用myredis 1、Dockerfile FROM hlw/java:8-jreRUN ln -sf /usr/share/z…

私密相冊管家-加密碼保護私人相冊照片安全

App Store史上最安全、最強大、最卓越的私密相冊App&#xff01;再也不用擔心私密照片視頻被別人看見了&#xff01;?私密相冊為你提供多重密碼保護機制、簡單便捷的照片存儲空間&#xff0c;完美地將你的私密照片遠離一切惡意偷窺者的窺探&#xff01; 【產品功能】? √ 支…

Redis—持久化

這里寫目錄標題 AOF三種寫回策略寫回策略的優缺點AOF 重寫機制AOF后臺重寫AOF優缺點使用命令 RDBRDB 持久化的工作原理執行快照時&#xff0c;數據能被修改嗎RDB 持久化的優點RDB 持久化的缺點 混合持久化大key對持久化的影響 AOF 保存寫操作命令到日志的持久化方式&#xff0…

開源數據庫Mysql_DBA運維實戰 (DML/DQL語句)

DML/DQL DML INSERT 實現數據的 插入 實例&#xff1a; DELETE 實現數據的 刪除 實例&#xff1a; UPDATE 實現數據的 更新 實例1&#xff1a; 實例2&#xff1a; 實例3&#xff1a; DQL DML/DQL DML語句 數據庫操縱語言&#xff1a; 插入數據INSERT、刪除數據DELE…

2023年即將推出的CSS特性對你影響大不大?

Google開發者大會每年都會提出有關于 Web UI 和 CSS 方面的新特性&#xff0c;今年又上新了許多新功能&#xff0c;今天就從中找出了影響最大的幾個功能給大家介紹一下 :has :has() 可以通過檢查父元素是否包含特定子元素或這些子元素是否處于特定狀態來改變樣式&#xff0c;也…

Python|OpenCV-繪制圖形和添加文字的方法(2)

前言 本文是該專欄的第2篇,后面將持續分享OpenCV計算機視覺的干貨知識,記得關注。 OpenCV作為一個強大的計算機視覺功能庫,除了能解決圖像處理和計算機視覺任務之外,它還有著非常豐富的圖像繪制功能。可以說,不論是在計算機視覺任務中標記目標領域,還是在圖像上繪制一些…

二刷LeetCode--155. 最小棧(C++版本),思維題

思路:本題需要使用兩個棧,一個就是正常棧,執行出入操作,另一個棧只負責將對應的最小值進行保存即可.每次入棧的時候,最小值棧的棧頂也需要入棧元素,不過這個元素是最小值,那么就需要進行比較,因此在getmin()的時候只需要將最小值棧的棧頂元素彈出即可.初始化的時候只需要將最小…

【vue3】點擊按鈕彈出卡片,點擊卡片中的取消按鈕取消彈出的卡片(附代碼)

實現思路&#xff1a; 在按鈕上綁定一個點擊事件&#xff0c;默認是true&#xff1b;在export default { }中注冊變量給卡片標簽用v-if判斷是否要顯示卡片&#xff0c;ture則顯示&#xff1b;在卡片里面寫好你想要展示的數據&#xff1b;給卡片添加一個取消按鈕&#xff0c;綁…

JVM G1垃圾回收機制介紹

G1(Garbage First)收集器 (標記-整理算法)&#xff1a; Java堆并行收集器&#xff0c;G1收集器是JDK1.7提供的一個新收集器&#xff0c;G1收集器基于“標記-整理”算法實現&#xff0c;也就是說不會產生內存碎片。此外&#xff0c;G1收集器不同于之前的收集器的一個重要特點是&…

vue中 contenteditable 中如何將光標聚焦到最后位置

場景: 1. 在vue中, 又在for循環中, 給div元素配置contenteditable屬性, 但是使用不了v-model綁定 2. 點擊外部元素需要聚焦并將光標聚焦到最后位置 方案: 1. 使用vue-input-contenteditable第三方包, 可以使用v-model綁定, // 下載 yarn add vue-input-contenteditable…

每日一學——網絡層

網絡層是計算機網絡體系結構中的一個關鍵層級。它負責將數據從源主機發送到目標主機&#xff0c;通過路由選擇和路徑管理實現在不同網絡之間的數據傳輸。以下是網絡層的詳細資料&#xff0c;包括應用、案例和常見問題&#xff1a; 功能&#xff1a;網絡層的主要功能是提供端到端…

[Poetize6] IncDec Sequence

題目描述 給定一個長度為 n 的數列 a_1,a_2,...,a_n&#xff0c;每次可以選擇一個區間[l,r]&#xff0c;使這個區間內的數都加 1 或者都減 1。 請問至少需要多少次操作才能使數列中的所有數都一樣&#xff0c;并求出在保證最少次數的前提下&#xff0c;最終得到的數列有多…