原文链接

https://dave.cheney.net/paste/concurrency-made-easy.pdf

原文对 go 的并发中常常出现的bug 提出了一些建议,本文是对原文的建议做一些总结和自己的见解

不必要的goroutine的使用

如果一个goroutine在还没有获得另一个goroutine的返回结果时,无法取得进展,那么不如放弃goroutine的使用,只用这个goroutine完成工作

package main import ("fmt""log""net/http""runtime") func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello, GopherCon SG")})         go func() {if err := http.ListenAndServe(":8080", nil); err != nil {log.Fatal(err)}}()         for {}  // 第一种阻塞的方式//for {//        runtime.Gosched()       //第二种//}//select {}       //第三种}

以上例子中的三种方法来阻塞goroutine 都是治标不治本 。
倒不如直接把 ListenAndServe 放在 同一个goroutine中运行

package main import ("fmt""log""net/http") func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello, GopherCon SG")})         if err := http.ListenAndServe(":8080", nil); err != nil {log.Fatal(err)}}

按照获取锁和channel值的相反顺序释放它们

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {sem <- 1go func() {defer func() {wg.Done()<-sem}()if err := fetch(repo); err != nil {errChan <- err}}()}wg.Wait()close(sem)close(errChan)return <-errChan}

在这个例子中

defer func() {wg.Done()<-sem
wg.Wait()
close(sem)

在这两处地方, close(sem) happens after wg.Wait() ,因此 close(sem) 也 happens after wg.Done()
当wg.Done() 后,waitgroup不再等待 ,所以无法知晓 <-sem happens-before close(sem) 还是 close(sem) happens-before <-sem ,所以可能会出现bug

这种情况的一种解决办法就是 把 <-sem 写在goroutine 里面 <-sem happens-before wg.Done()

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {sem <- 1go func() {defer wg.Done()if err := fetch(repo); err != nil {errChan <- err}<-sem   //这里}()}wg.Wait()close(sem)close(errChan)return <-errChan}

channel不像其他的资源,没有必要为了释放它们而close

close()的作用是关闭通道,让cahnnel不再接收值,而不是释放channel 资源

Acquire semaphores when you’re ready to use them.

尽管goroutine的创建和调度都很便宜,但是它们所使用的资源,比如文件、socket、带宽等等,通常都比较稀缺。使用通道作为信号量来限制正在进行的工作的模式非常常见。
但是,为了确保不会过度地阻塞goroutine的代码加载工作,请在准备好使用它们时获取信号量,而不是期望使用它们时获取信号量。

避免出现goroutine 的数据竞争

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {   //这里sem <- 1go func() {defer wg.Done()if err := fetch(repo); err != nil {    //这里errChan <- err}<-sem   }()}wg.Wait()close(sem)close(errChan)return <-errChan

还是这个例子中,每个goroutine都需要for循环中的repo,所以会出现所有 goroutine 都会试图去读最后一个repo的值

解决办法是将 repo 作为参数传进goroutine

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for i := range repos {go func(repo string) {   //这里defer wg.Done()sem <- 1if err := fetch(repo); err != nil {errChan <- err}<-sem}(repos[i]) //这里}wg.Wait()close(errChan)return <-errChan}

避免匿名函数 和 goroutine 混合使用

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {go worker(repo, sem, &wg, errChan)}wg.Wait()close(errChan)return <-errChan} func worker(repo string, sem chan int, wg *sync.WorkGroup, errChan chan err) {defer wg.Done()sem <- 1if err := fetch(repo); err != nil {errChan <- err}<-sem}

这样写就会避免出现上文 repo 的数据竞争

在你开始一个goroutine之前,要知道它何时以何种方式停止

func restore(repos []string) error {errChan := make(chan error, 1) //这里sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {go worker(repo, sem, &wg, errChan)}wg.Wait()close(errChan)return <-errChan} func worker(repo string, sem chan int, wg *sync.WorkGroup, errChan chan err) {defer wg.Done()sem <- 1if err := fetch(repo); err != nil {errChan <- err  //这里}<-sem}

还是这个例子,注意errChan 是一个缓冲为1的channel,如果所有的go worker 都出现err 那么errChan就会阻塞

一种解决办法是 将errChan的大小设置为len(repos)

func restore(repos []string) error {errChan := make(chan error, len(repos))  //这里sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {go worker(repo, sem, &wg, errChan)}wg.Wait()close(errChan)return <-errChan} func worker(repo string, sem chan int, wg *sync.WorkGroup, errChan chan err) {defer wg.Done()sem <- 1if err := fetch(repo); err != nil {errChan <- err}<-sem}

我们还可以结合select的使用来解决 ,不必为所有可能的错误开辟空间 ,而是可以使用非阻塞发送将错误放入errChan(如果不存在的话),否则将丢弃该值。

func restore(repos []string) error {errChan := make(chan error, 1)sem := make(chan int, 4) // four jobs at oncevar wg sync.WaitGroupwg.Add(len(repos))for _, repo := range repos {go worker(repo, sem, &wg, errChan)}wg.Wait()close(errChan)return <-errChan} func worker(repo string, sem chan int, wg *sync.WorkGroup, errChan chan err) {defer wg.Done()sem <- 1if err := fetch(repo); err != nil {select {case errChan <- err:// we're the first worker to faildefault:// some other failure has already happened}}<-sem}

对 concurrency-made-easy 文章的总结相关推荐

  1. 分享76个ASP新闻文章源码,总有一款适合您

    分享76个ASP新闻文章源码,总有一款适合您 76个ASP新闻文章源码下载链接:https://pan.baidu.com/s/1hWoO9AwlTcmzcO6mDlwshA?pwd=60i6  提取 ...

  2. Erlang之父Joe Armstrong确认将参加中国软件开发者大会

    2019独角兽企业重金招聘Python工程师标准>>> 2016年11月18日-20日,由CSDN重磅打造的年终技术盛会,SDCC 2016中国软件开发者大会将在北京举行,大会面向国 ...

  3. Erlang之父Joe Armstrong确认出席SDCC 2016中国软件开发者大会,并发表主题演讲

    2016年11月18日-20日,由CSDN重磅打造的年终技术盛会,SDCC 2016中国软件开发者大会将在北京举行,大会面向国内外的中高端技术人员,聚焦最前沿技术及一线的实践经验,从而助力企业的技术升 ...

  4. 大厂后台开发基本功修炼路线和经典资料

    hi ,大家好!一般来说,如果想跳槽,年后是比较适合的,对于校招来说,今年的春招和秋招马上就要来了,为了自己的职业发展,大家要把握好一切可以把握的机会: 本文内容来自‍‍‍‍‍:极客星球(首发)(基本 ...

  5. python获取当前线程数量_python 线程数

    python 多线程 真正的多线程吗? 对于多核处理器,在同一时间确实可以多个线程独立运行,但在Python中确不是这样的了.原因在于,python虚拟机中引入了GIL这一概念.GIL(Global ...

  6. java timer 动画_java – 使用Swing动画进行计时

    Jonas已经给出了答案(使用Swing计时器),但是解释为什么你没有看到动画可能是有用的,以及为什么计时器是这个问题的最佳解决方案. 为什么我看不到不同的重绘 当您调用JComponent #rep ...

  7. So easy!10 行代码写个“让你惊叹”的文章生成器 | 原力计划

    作者 | liu志军 责编 | 屠敏 出品 | CSDN 博客 前几天,GitHub 有个开源项目特别火,只要输入标题就可以生成一篇长长的文章. 背后实现代码一定很复杂吧,里面一定有很多高深莫测的机器 ...

  8. 计算机顶级会议的历年最佳文章 (1996-2013)

    本人博客地址:http://blog.csdn.net/wanrenwangxuejing Best Paper Awards in Computer Science (since 1996) ByC ...

  9. 论文:Insights on Transfer Optimization: Because Experience is the Best Teacher(1)文章结构以及以及自己的感觉

     链接: IEEE Xplore Search Results https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&a ...

  10. 论文: Data-Driven Evolutionary Optimization: An Overview and case studies(1) 数据驱动概念,文章结构,大数分类

    声明: 只作为自己阅读论文的相关笔记记录,理解有误的地方还望指正  论文下载链接: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumb ...

最新文章

  1. 盖茨的背后:坚持到最后一分钟
  2. 再也不怕复现论文!arXiv携手Papers with Code,提交论文+上传代码一步到位
  3. python:进程操作
  4. 宁波网络推广浅析网站在优化关键词需注意哪些事项?
  5. Hi3516A开发-- 常见问题FAQs
  6. Druid如何自动根据URL自动识别DriverClass的
  7. JS任务队列--笔记
  8. python字典保存为excel_python将字典列表导出为Excel文件的方法
  9. Cortex M3/M4 学习摘要(二)
  10. 含有Date类型的对象或集合转换成json时的问题
  11. js读写json文件
  12. 参加电子工业出版社博文视点举办的作者高峰论坛有感
  13. 超定方程组最小二乘matlab,超定方程组最优解(最小二乘解)推导
  14. codesmith mysql 注释_代码生成工具:CodeSmith 安装、改造适配Mysql 字段注释、DLL修改 及批量生成实体类代码...
  15. 二维数组传参(二维数组作为函数参数)
  16. 2.14 Whisper和Swarm
  17. 连接超时与读取超时概述
  18. 史上最强最逼真的游戏
  19. SAP 物料号系统内部编码导入BAPI报错(E M_ 17)
  20. java遍历json数据_Java 如何遍历JsonObject对象

热门文章

  1. 链表 java 实现
  2. vue的微信语音功能,录音+对接口返回amr音频播放
  3. 一种绘制有向图的方法<TSE93> - 2. 最优层级分配
  4. r语言结构方程模型可视化_模型一个结构方程模型(SEM)的简单实验
  5. 10行Python代码实现抽奖助手自动参与抽奖
  6. 驱动里面如何操作regulator
  7. JS--历史搜索记录的实现
  8. rgb sw 线主板接口在哪_华硕ROG STRIX LC 360 RGB一体式水冷:与ROG主板更配
  9. 简述自动化测试实习收获
  10. 水溶性羧基化 CdSe/ZnS 量子的特点