go 并发编程
并發編程就是可以讓你的程序不是順序執行的,而是可以多個分支同時進行,在go中,這個分支也被稱為協程goroutine(輕量級線程)。
Go語言中使用goroutine非常簡單,只需要在調用函數的時候在前面加上go關鍵字,就可以為一個函數創建一個goroutine。
一個goroutine必定對應一個函數,可以創建多個goroutine去執行相同的函數。
在程序啟動時,Go程序就會為main()函數創建一個默認的goroutine。main函數結束,所有的goroutine都會結束。
package mainimport ("fmt""sync" )var total struct{sync.Mutexvalue int }func worker(wg *sync.WaitGroup) {defer wg.Done() //計數器值減一for i := 0; i < 100; i++ {total.Lock() //加鎖,其他線程想加鎖會發生阻塞,保證加鎖范圍內的語句同一時刻只會有一個線程訪問total.value += itotal.Unlock() //解鎖} }func main() {var wg sync.WaitGroup //計數器,初始值0wg.Add(2) //對計數器進行加2wg.Add(1)go worker(&wg) //開啟go routinego worker(&wg) //計數器要傳引用go worker(&wg)wg.Wait() //阻塞,知道計數器值變為0fmt.Println(total.value) }上例中可以看到有加鎖語句,對多線程模型的程序而言,原子性操作進行加鎖和解鎖都是有必要的。所謂的原子性操作,就是并發編程中“最小的且不可并行化的”操作。假如上文中的total.value += i同時有兩個線程執行這句,假如此時value=2,i都為5,則執行這兩句后,得到的結果為7,而不是12,所以可能會導致結果不正確。上面的是加的是互斥鎖,go中還有讀寫鎖sync.RWMutex
我們也可用sync/atomic包來進行原子性操作(推薦):
func worker(wg *sync.WaitGroup) {defer wg.Done() //計數器值減一for i := 0; i < 100; i++ {atomic.AddInt64(&total.value, int64(i))} }順序一致性內存模型
順序一致性內存模型有兩大特性。
- 一個線程中所有操作必須按照程序的順序來執行。
- (不管程序是否同步)所有線程都只能看到一個單一的操作執行順序。在順序一致性內存模型中,每個操作都必須原子執行且立刻對所有線程可見。
在Go語言中,同一個Goroutine線程內部,順序一致性的模型是得到保證的。但是不同的Goroutine之間,并不滿足順序一致性的內存模型。需要通過明確定義的同步事件來作為同步的參考。
goroutine奉行通過通信來共享內存,其中,通道是在goroutine之間進行同步的主要方法。用法如下:
package mainimport "fmt"var chan1 = make(chan bool) //建立一個無緩存通道(通道的緩存大小是0) var chan2 = make(chan bool)func work(job string) {fmt.Println("I want to work: ", job)chan1 <- true // 無緩存通道的發送操作總在對應的接收操作前發生 推薦的做法// 無緩存通道只在有人接收的時候才能發送值,否則就會一直在阻塞。 }func study(book string) {fmt.Println("I want to study: ", book)close(chan1) //關閉通道后,接收者可以從通道中接收到零值 }func play(game string) {fmt.Println("I want to play: ", game)<- chan2 // 無緩存通道的接收總在對應該通道的發生完成前 }func main() {go work("程序員")go study("go語言")go play("王者榮耀")<- chan1<- chan1chan2 <- false }對于帶緩存的通道,其中C是通道的緩存大小,則表示通道最多可儲存C個值,達到這個值還沒有被接收,則發送會被阻塞。我們可以通過控制通道的緩存大小來控制并發執行的goroutine的最大數目??梢允褂脙戎玫膌en函數獲取通道內元素的數量,使用cap函數獲取通道的容量。
例如:(此處僅當示例)
package mainimport "fmt"var chan1 = make(chan bool) //建立一個無緩存通道(通道的緩存大小是0) var chan2 = make(chan bool) var chan3 = make(chan bool, 3)func work(job string) {fmt.Println("I want to work: ", job)//chan1 <- true // 無緩存通道的發送操作總在對應的接收操作前發生chan3 <- true }func study(book string) {fmt.Println("I want to study: ", book)//close(chan1) //關閉通道后,接收者可以從通道中接收到零值chan3 <- true }func play(game string) {fmt.Println("I want to play: ", game)//<- chan2 // 無緩存通道的接收總在對應該通道的發生完成前chan3 <- true }func main() {go work("程序員")go study("go語言")go play("王者榮耀")<- chan3<- chan3<- chan3//<- chan1//chan2 <- false }通過select和default分支可以很容易實現一個goroutine的退出控制:
package mainimport ("fmt""time" )func work2(channel chan bool, phone chan int) {for {time.Sleep(time.Second)select {default:fmt.Println("I am 工作")case <- channel:fmt.Println("I am 下班")phone <- 1}} }func main() {ch := make(chan bool)phone := make(chan int, 10)for i := 0; i < 10; i++ {go work2(ch, phone)}time.Sleep(time.Second * 3)close(ch)for i := 0; i < 10; i++ {<-phone} }上例也可用sync.WaitGroup,結合defer來進行改進。
package mainimport ("fmt""sync""time" )func work2(channel chan bool, wg *sync.WaitGroup) {defer wg.Done()for {time.Sleep(time.Second)select {default:fmt.Println("I am 工作")case <- channel:fmt.Println("I am 下班")return}} }func main() {ch := make(chan bool)var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go work2(ch, &wg)}time.Sleep(time.Second * 3)close(ch)wg.Wait() }?關閉通道close
關于關閉通道需要注意的事情是,只有在通知接收方goroutine所有的數據都發送完畢的時候才需要關閉通道。通道是可以被垃圾回收機制回收的,它和關閉文件是不一樣的,在結束操作之后關閉文件是必須要做的,但關閉通道不是必須的。
關閉后的通道有以下特點:
1.對一個關閉的通道再發送值就會導致panic。2.對一個關閉的通道進行接收會一直獲取值直到通道為空。3.對一個關閉的并且沒有值的通道執行接收操作會得到對應類型的零值。4.關閉一個已經關閉的通道會導致panic。如果你的通道不往里存值了記得關閉通道
func main() {c := make(chan int)go func() {for i := 0; i < 5; i++ {c <- ifmt.Println("協程", i)}close(c) //假如不關閉通道,下面的線程還是會認為這個通道能夠取出值,會陷入deadlock}()for {if data, ok := <-c; ok { // 用ok判斷channel是否能取出值fmt.Println(data)} else {break}}fmt.Println("main結束") }context包 上下文
使用例子:
//素數篩,并用context包來避免后臺goroutine內存泄漏 package mainimport ("context""fmt" )//生成自然數序列 func GeneralNatural(ctx context.Context) chan int {ch := make(chan int)go func() {for i := 2; ; i++ {select {case <-ctx.Done():returndefault:ch <- i}}}()return ch }//通道過濾器: 刪除能被素數整除的數 func PrimerFilter(ctx context.Context, in <- chan int, primer int) chan int{out := make(chan int)go func() {for {var i intif i = <-in; i % primer != 0 {select {case <-ctx.Done():returndefault:out <- i //新的序列,比原來少了能被primer整除的數}}}}()return out }func main() {// 通過Context控制后臺Goroutine狀態ctx, cancel := context.WithCancel(context.Background())//返回其子context和取消函數cancelch := GeneralNatural(ctx) // 生產自然數序列 2,3,4,5,.....for i := 0; i < 100; i++ {prime := <-ch //新出現的素數fmt.Printf("%v: %v\n", i+1, prime)ch = PrimerFilter(ctx, ch, prime) //過濾掉能被新出現的素數整除的數}cancel()// cancel調用,即會往子Context的Done通道發送消息 }總結
- 上一篇: 手机回收站的照片超过30天删除了怎么恢复
- 下一篇: 海康卫视网页端查看监控,一直提示下载插件