|
|
51CTO旗下网站
|
|
移动端

10.4.2 协程同步

《Go语言编程入门与实战技巧》第10章并发编程,本章Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为协程(goroutine)时,Go语言会将其视为一个独立的工作单元,这个单元会被调度到可用的逻辑处理器上执行。本节为大家介绍协程同步。

作者:黄靖钧来源:电子工业出版社|2018-09-23 09:54

10.4.2  协程同步

通道可以被关闭,尽管它们和文件不同,不必每次都关闭,只有在当需要告诉接收者不会再提供新的值的时候,才需要关闭通道。只有发送者需要关闭通道,接收者永远不会需要。继续看示例:

  1. // 示例代码10-5  
  2. package main  
  3.  
  4. import (  
  5.     "fmt"  
  6.     "time"  
  7. )  
  8.  
  9. func main() {  
  10.     ch :make(chan string)  
  11.  
  12.     go sendData(ch)  
  13.     go getData(ch)    
  14.  
  15.     time.Sleep(1e9)  
  16. }  
  17.  
  18. func sendData(ch chan string) {  
  19.     ch <- "纽约"  
  20.     ch <- "华盛顿"  
  21.     ch <- "伦敦"  
  22.     ch <- "北京"  
  23.     ch <- "东京"  
  24. }  
  25.  
  26. func getData(ch chan string) {  
  27.     var input string  
  28.     // time.Sleep(1e9)  
  29.     for {  
  30.         input = <-ch 
  31.         fmt.Printf("%s ", input)  
  32.     }  
  33. }  
  34. /*  
  35. 返回:  
  36. 纽约 华盛顿 伦敦 北京 东京  
  37. */  

如何在通道的sendData()完成的时候发送一个信号,getData()又如何检测到通道是否关闭或阻塞?

第一种方法可以通过函数close(ch)来完成:这个将通道标记为无法通过发送操作<-接收更多的值;给已经关闭的通道发送或者再次关闭都会导致运行时的panic。在创建一个通道后使用defer语句是个不错的办法(类似这种情况):

  1. ch :make(chan float64)  
  2. defer close(ch)  

第二种方法可以使用逗号、ok操作符:用来检测通道是否被关闭:

  1. v, ok :<-ch   // 如果 v 接收到一个值,ok 的值则为 true  

通常和if语句一起使用:

  1. if v, ok :<-ch; ok {  
  2.   process(v)  
  3. }  

或者在for循环中接收的时候,当关闭或者阻塞的时候使用break:

  1. v, ok :<-ch 
  2. if !ok {  
  3.   break  
  4. }  
  5. process(v)  

可以通过_ = ch <- v来实现非阻塞发送,因为空标识符获取到了发送给ch的所有内容。

实现非阻塞通道的读取,需要使用select:

  1. // 示例代码10-6  
  2. package main  
  3.  
  4. import "fmt"  
  5.  
  6. func main() {  
  7.     ch :make(chan string)  
  8.     go sendData(ch)  
  9.     getData(ch)  
  10. }  
  11.  
  12. func sendData(ch chan string) {  
  13.     ch <- "纽约"  
  14.     ch <- "华盛顿"  
  15.     ch <- "伦敦"  
  16.     ch <- "北京"  
  17.     ch <- "东京"  
  18.     close(ch)  
  19. }  
  20.  
  21. func getData(ch chan string) {  
  22.     for {  
  23.         input, open :<-ch 
  24.         if !open {  
  25.             break  
  26.         }  
  27.         fmt.Printf("%s ", input)  
  28.     }  
  29. }  

改变了以下代码:

(1)现在只有sendData()是协程,getData()和main()在同一个线程中。

  1. go sendData(ch)  
  2. getData(ch)  

(2)在sendData()函数的最后,关闭了通道:

  1. func sendData(ch chan string) {  
  2.     ch <- "纽约"  
  3.     ch <- "华盛顿"  
  4.     ch <- "伦敦"  
  5.     ch <- "北京"  
  6.     ch <- "东京"  
  7.     close(ch)  
  8. }  

(3)在for循环的getData()中,在每次接收通道的数据之前都使用if !open来检测:

  1. for {  
  2.   input, open :<-ch 
  3.   if !open {  
  4.     break  
  5.   }  
  6.   fmt.Printf("%s ", input)  
  7. }  

使用for-range语句来读取通道是更好的办法,因为这会自动检测通道是否关闭:

  1. for input :range ch {  
  2.   process(input)  
  3. }  

阻塞和生产者-消费者模式:在通道迭代器中,两个协程经常是一个阻塞另外一个。如果程序工作在多核的机器上,大部分时间只用到一个处理器。可以通过使用带缓冲(缓冲空间大于0)的通道来改善。比如,缓冲大小为100,迭代器在阻塞之前,至少可以从容器获得100个元素。如果消费者协程在独立的内核运行,就有可能让协程不会出现阻塞。

由于容器中元素的数量通常是已知的,需要让通道有足够的容量放置所有的元素。这样,迭代器就不会阻塞(尽管消费者协程仍然可能阻塞)。然后,这样有效地加倍了迭代容器所需要的内存使用量,所以通道的容量需要限制最大值。记录运行时间和性能测试可以帮助你找到最小的缓存容量带来最好的性能。


喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书会第9群:808517103

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

数据挖掘:概念与技术

本书第1版曾被KDnuggets的读者评选为最受欢迎的数据挖掘专著,是一本可读性极佳的教材。它从数据库角度全面系统地介绍了数据挖掘的基本概念...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊