Go goroutine同步
CarWelker
9年前
<p>来自: <a href="/misc/goto?guid=4959670135417887051" rel="nofollow">http://andrewliu.in/2016/04/08/Go-goroutine同步/</a></p> <p>本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循 <a href="/misc/goto?guid=4958876358311513780" rel="nofollow,noindex">署名-非商业用途-保持一致</a> 的创作共用协议.</p> <p>出现问题场景: 一个函数run()中包含多个goroutine函数并发, 这些goroutine函数会生成中间文件, 被run()函数运行结束后的check()函数检查. 当goroutine并发时, 并不会阻塞run()的上下文, 可能导致的情况为run()函数执行完毕( 但其中的goroutine并发函数没有执行完毕 ), 导致check()函数执行失败.</p> <p>所以我们需要一种操作, 直到当前所有goroutine没有执行完毕, 才进行下一步操作</p> <p>所以需要 goroutine同步 , go提供了 sync包 和 channel机制 来解决goroutine之间的同步问题</p> <h2>sync.WaitGroup</h2> <pre> A WaitGroup waits foracollectionofgoroutinestofinish. The main goroutine calls Addtosetthenumberofgoroutinestowaitfor. Theneachofthegoroutines runsandcalls Done when finished. Atthesametime, Wait can be usedtoblockuntilall goroutines have finished.-- 出自官方文档 </pre> <p>大概意思是: WaitGroup 等待一组goroutinue执行完毕. 主goroutinue调用 Add 设置等待的goroutinue数量. 每个goroutinue应该在执行结束时调用 Done . Wait 会阻塞知道所有goroutinue执行完毕.</p> <p>WaitGroup 的用于某个地方需要创建多个goroutine,并且一定要等它们都执行完毕后再继续执行接下来的操作.</p> <p>可以把 WaitGroup 看作一个类似任务队列的结构. Add想队列增加任务, Done完成任务, Wait在队列不空的时候阻塞在哪里.</p> <pre> // 官方文档中的example packagemain import( "fmt" "sync" "net/http" ) funcmain() { varwg sync.WaitGroup// 声明一个WaitGroup变量 varurls = []string{ "http://www.baidu.org/", "http://www.alibaba.com/", "http://www.qq.com/", } for_, url :=rangeurls { wg.Add(1)// WaitGroup的计数加1 // Launch a goroutine to fetch the URL. gofunc(urlstring) { deferwg.Done()// goroutinue完成后, WaitGroup的计数-1 // Fetch the URL. http.Get(url) fmt.Println(url); }(url) } // Wait for all HTTP fetches to complete. wg.Wait() // 等待所有goroutinue完成 } </pre> <h2>channel</h2> <p>channel同样可以用来同步goroutinue</p> <p>channel四种操作</p> <pre> // make创建chennel, 第一个参数为channel的类型, 第二个参数为channel缓冲区的大小, 为0或者不传入该参数则表示没有缓冲区 exampleChannel := make(chanint,100) // 放入数据到channel (channel <- data) exampleChannel <-1 // 取出数据 (<-channel) number := <-exampleChannel // 关闭channel (通过close()函数) close(exampleChannel) </pre> <p>channel是一种 阻塞管道 , 是自动阻塞的. 如果 channel 满了, 对channel放入数据的操作就会阻塞, 直到有某个routine从channel中取出数据, 这个放入数据的操作才会执行. 相反同理, 如果管道是空的, 一个从channel取出数据的操作就会阻塞,直到某个routine向这个channel中放入数据, 这个取出数据的操作才会执行(原理非常类似阻塞型socket的读写缓冲区)</p> <pre> // 上面的样例代码使用chan同步的方式来重写 packagemain import( "fmt" "net/http" ) funcmain() { varurls = []string{ "http://www.baidu.org/", "http://www.alibaba.com/", "http://www.qq.com/", } doneChannel := make(chanint,len(urls))// 创建channel for_, url :=rangeurls { // Increment the WaitGroup counter. // Launch a goroutine to fetch the URL. gofunc(urlstring) { // Decrement the counter when the goroutine completes. // Fetch the URL. http.Get(url) doneChannel <-1// 向channel里放入数据表示完成操作 fmt.Println(url); }(url) } // Wait for all HTTP fetches to complete. fori :=0; i <len(urls); i++{ <-doneChannel // 当可以取出len(urls)个数据时, 表示所有goroutinue都完成 } fmt.Printf("Finish..\n") } </pre> <p>当任务的数量不固定</p> <pre> packagemain import( "fmt" ) functest_chan(groutineChanchanint, feedbackChanchanstring) { deferfunc() { <-groutineChan feedbackChan <- "finish" }() // do some process // ... } funcmain() { var( goroutineChan chanint=make(chanint,20) feedbackChan chanstring=make(chanstring,10000) counter int finish int ) fori :=0; i <1000; i++ { goroutineChan <-1 counter++ gotest_chan(goroutineChan, feedbackChan) } for{ msg := <-feedbackChan // 从channel取出字符串 ifmsg =="finish"{ finish++ // 没完成一个完成计数器加一 } iffinish == counter {//当完全计数器等于计数器表示所有的goroutine完成 break } } fmt.Printf("Finish..\n") } </pre> <h2>参考链接</h2> <ul> <li><a href="/misc/goto?guid=4959670135534800154" rel="nofollow,noindex">go语言WaitGroup用法</a></li> <li><a href="/misc/goto?guid=4959670135612434332" rel="nofollow,noindex">golang学习笔记之—WaitGroup</a></li> </ul>