对 concurrency-made-easy 文章的总结
原文链接
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 文章的总结相关推荐
- 分享76个ASP新闻文章源码,总有一款适合您
分享76个ASP新闻文章源码,总有一款适合您 76个ASP新闻文章源码下载链接:https://pan.baidu.com/s/1hWoO9AwlTcmzcO6mDlwshA?pwd=60i6 提取 ...
- Erlang之父Joe Armstrong确认将参加中国软件开发者大会
2019独角兽企业重金招聘Python工程师标准>>> 2016年11月18日-20日,由CSDN重磅打造的年终技术盛会,SDCC 2016中国软件开发者大会将在北京举行,大会面向国 ...
- Erlang之父Joe Armstrong确认出席SDCC 2016中国软件开发者大会,并发表主题演讲
2016年11月18日-20日,由CSDN重磅打造的年终技术盛会,SDCC 2016中国软件开发者大会将在北京举行,大会面向国内外的中高端技术人员,聚焦最前沿技术及一线的实践经验,从而助力企业的技术升 ...
- 大厂后台开发基本功修炼路线和经典资料
hi ,大家好!一般来说,如果想跳槽,年后是比较适合的,对于校招来说,今年的春招和秋招马上就要来了,为了自己的职业发展,大家要把握好一切可以把握的机会: 本文内容来自:极客星球(首发)(基本 ...
- python获取当前线程数量_python 线程数
python 多线程 真正的多线程吗? 对于多核处理器,在同一时间确实可以多个线程独立运行,但在Python中确不是这样的了.原因在于,python虚拟机中引入了GIL这一概念.GIL(Global ...
- java timer 动画_java – 使用Swing动画进行计时
Jonas已经给出了答案(使用Swing计时器),但是解释为什么你没有看到动画可能是有用的,以及为什么计时器是这个问题的最佳解决方案. 为什么我看不到不同的重绘 当您调用JComponent #rep ...
- So easy!10 行代码写个“让你惊叹”的文章生成器 | 原力计划
作者 | liu志军 责编 | 屠敏 出品 | CSDN 博客 前几天,GitHub 有个开源项目特别火,只要输入标题就可以生成一篇长长的文章. 背后实现代码一定很复杂吧,里面一定有很多高深莫测的机器 ...
- 计算机顶级会议的历年最佳文章 (1996-2013)
本人博客地址:http://blog.csdn.net/wanrenwangxuejing Best Paper Awards in Computer Science (since 1996) ByC ...
- 论文: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 ...
- 论文: Data-Driven Evolutionary Optimization: An Overview and case studies(1) 数据驱动概念,文章结构,大数分类
声明: 只作为自己阅读论文的相关笔记记录,理解有误的地方还望指正 论文下载链接: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumb ...
最新文章
- 盖茨的背后:坚持到最后一分钟
- 再也不怕复现论文!arXiv携手Papers with Code,提交论文+上传代码一步到位
- python:进程操作
- 宁波网络推广浅析网站在优化关键词需注意哪些事项?
- Hi3516A开发-- 常见问题FAQs
- Druid如何自动根据URL自动识别DriverClass的
- JS任务队列--笔记
- python字典保存为excel_python将字典列表导出为Excel文件的方法
- Cortex M3/M4 学习摘要(二)
- 含有Date类型的对象或集合转换成json时的问题
- js读写json文件
- 参加电子工业出版社博文视点举办的作者高峰论坛有感
- 超定方程组最小二乘matlab,超定方程组最优解(最小二乘解)推导
- codesmith mysql 注释_代码生成工具:CodeSmith 安装、改造适配Mysql 字段注释、DLL修改 及批量生成实体类代码...
- 二维数组传参(二维数组作为函数参数)
- 2.14 Whisper和Swarm
- 连接超时与读取超时概述
- 史上最强最逼真的游戏
- SAP 物料号系统内部编码导入BAPI报错(E M_ 17)
- java遍历json数据_Java 如何遍历JsonObject对象