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