好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

Go并发编程基础.doc

9页
  • 卖家[上传人]:gg****m
  • 文档编号:209689596
  • 上传时间:2021-11-11
  • 文档格式:DOC
  • 文档大小:77.50KB
  • / 9 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • Go并发编程基础本文是一篇并发编程方面的入门文章,以Go语言编写示例代码, 内容涵盖:运行期并发线程(coroutines)基本的同步技术(管道和锁)Go 语言中基本的并发模式死锁和数据竞争并行计算在开始阅读本文之前,你应该知道如何编写简单的Go程序如 果你熟悉的是C/C++、Java或Python之类的语言,那么Go语言之旅 能提供所有必要的背景知识也许你还有兴趣读一读为C++程序员准 备的Go语言教程或为Java程序员准备的Go语言教程Go允许使用go语句开启一个新的运行期线程,即goroutine,以 一个不同的、新创建的goroutine来执行一个函数同一个程序中的 所有goroutine共享同一个地址空间Goroutine非常轻量,除了为之分配的栈空间,其所占用的内存 空间微乎其微并且其栈空间在开始时非常小,之后随着堆存储空间 的按需分配或释放而变化内部实现上,goroutine会在多个操作系 统线程上多路复用如果一个goroutine阻塞了一个操作系统线程, 例如:等待输入,这个线程上的其他goroutine就会迁移到其他线程, 这样能继续运行开发者并不需要关心/担心这些细节。

      下面所示程序会输出“ Hello from main goroutine ”也可能会输 出 “Hello from anothergoroutine^,具体依赖于两个 goroutine 哪个先 结束接下来的这个程序,多数情况下,会输出“ Hello from main coroutine” 和 Hello from anothergoroutine 输出的顺序不确定 但还有另一个可能性是:第二个goroutine运行得极其慢,在程序结 束之前都没来得及输出相应的消息下面则是一个相对更加实际的示例,其中定义了一个函数使用并 发来推迟触发一个事件你可能会这样使用Publish函数:这个程序,绝大多数情况下,会输出以下三行,顺序固定,每行 输出之间相隔5秒一般来说,通过睡眠的方式来编排线程之间相互等待是不太可能 的下一章节会介绍G语言中的一种同步机制-管道,并演示如 何使用管道让一个goroutine等待另一个goroutine2. 管道(channel)管道是Go语言的一个构件,提供一种机制用于两个goroutine 之间通过传递一个指定类型的值来同步运行和通讯操作符<■用于 指定管道的方向,发送或接收。

      如果未指定方向,则为双向管道管道是引用类型,基于make函数来分配如果通过管道发送一个值,则将<■作为二元操作符使用通过 管道接收一个值,则将其作为一元操作符使用:如果管道不带缓冲,发送方会阻塞直到接收方从管道中接收了值 如果管道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内; 如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值 接收方在有值可以接收之前会一直阻塞关闭管道(Close)close函数标志着不会再往某个管道发送值在调用close之后, 并且在之前发送的值都被接收后,接收操作会返回一个零值,不会阻 塞一个多返回值的接收操作会额外返回一个布尔值用来指示返回的 值是否发送操作传递的一个带有range 了句的for语句会依次读取发往管道的值,直到 该管道关闭:3. 同步下一个示例中,我们让Publish函数返回一个管道-用于在发 布text变量值时广播一条消息:注意:我们使用了一个空结构体的管道:struct*这明确地指明 该管道仅用于发信号,而不是传递数据我们可能会这样使用这个函数:这个程序会按指定的顺序输出以下三行内容最后一行在新闻(news) 一出就会立即输出。

      4. 死锁现在我们在Publish函数中引入一个bug:主程序还是像之前一样开始运行:输出第一行,然后等待5秒, 这时Publish函数开启的goroutine会输出突发新闻(breaking news), 然后退出,留下主goroutine独白等待此刻之后,程序无法再继续往下执行众所周知,这种情形即为死锁Go语言对于运行时的死锁检测具备良好的支持当没有任何 goroutine能够往前执行的情形发生时,Go程序通常会提供详细的错 误信息以下就是我们的问题程序的输出:大多数情况下找出Go程序中造成死锁的原因都比较容易,那么剩下的就是如何解决这个 bug 了 o5. 数据竞争(data race)死锁也许听起来令人挺忧伤的,但伴随并发编程真正灾难性的错 误其实是数据竞争,相当常见,也可能非常难于调试下面的这个函数就有数据竞争问题,其行为是未定义的例如, 可能输出数值1代码之后是一个可能性解释,试图搞清楚这一切是 如何发生得代码中的两个goroutine(假设命名为gl和g2)参与了一次竞争,我们无法获知操作会以何种顺序发生以下是诸多可能中的一种:凯从n中获取值0g2从n中获取值0型将值从0增大到lgl将1 写到ng2将值从0增大到辽2将1写到n程序输出n的值,当前为1“数据竞争(data race)”这名字有点误导的嫌疑。

      不仅操作的 顺序是未定义的,其实根本没有任何保证(no guarantees whatsoever)Q 编译器和硬件为了得到更好的性能,经常都会对代码进行上下内外的 顺序变换如果你看到一个线程处于中间行为状态时,那么当时的场 景可能就像下图所示的一样:避免数据竞争的唯一方式是线程间同步访问所有的共享可变数 据有几种方式能够实现这一目标Go语言中,通常是使用管道或 者锁sync和sync/atomic包中还有更低层次的机制可供使用,但木 文中不做讨论)Go语言中,处理并发数据访问的推荐方式是使用管道从一个 goroutine中往下一个goroutine传递实际的数据有格言说得好:“不 要通过共享内存来通讯,而是通过通讯来共享内存”以上代码中的管道肩负双重责任-从一个goroutine将数据传 递到另一个goroutine,并月•起到同步的作用:发送方goroutine会等 待另一个goroutine接收数据,接收方goroutine也会等待另一个 goroutine发送数据Go语言内存模型-要保证一个goroutine中对一个变量的读操 作得到的值正好是另一个goroutine中对同一个变量写操作产生的值,条件相当复杂, 但goroutine之间只要通过管道来共享所有可变数据,那么就能远离 数据竞争了。

      6. 互斥锁有时,通过显式加锁,而不是使用管道,来同步数据访问,可能 更加便捷Go语言标准库为这一目的提供了一个互斥锁- syn c.MuteXo要想这类加锁起效的话,关键之处在于:所有对共享数据的访问, 不管读写,仅当goroutine持有锁才能操作一个goroutine出错就足 以破坏掉一个程序,引入数据竞争因此,应该设计一个自定义数据结构,具备明确的API,确保所 有的同步都在数据结构内部完成下例中,我们构建了一个安全、易 于使用的并发数据结构,Atomiclnt,用于存储一个整型值任意数量 的goroutine都能通过Add和Value方法安全地访问这个数值7. 检测数据竞争竞争有时非常难于检测下例中的这个函数有一个数据竞争问题, 执行这个程序时会输出55555o尝试一下,也许你会得到一个不同的 结果sync.WaitGroup是Go语言标准库的一部分;用于等待一组 goroutine结束运行对于输出55555, 一个貌似合理的解释是:执行i++的goroutine 在其他goroutine执行打印语句之前就完成了 5次i++操作实际上变 量i更新后的值为其他goroutine所见纯属巧合。

      一个简单的解决方案是:使用一个局部变量,然后当开启新的 goroutine时,将数值作为参数传递:这次代码就对了,程序会输出期望的结果,女2403lo注意:goroutine之间的运行顺序是不确定的仍旧使用闭包,但能够避免数据竞争也是可能的,必须小心地让每个goroutine使用一个独有的变量数据竞争自动检测 一般来说,不太可能能够自动检测发现所有可能的数据竞争情况,但Go (从版本1.1开始)有一个强大的数据竞争检测器这个工具用起来也很简单:只要在使用go命令时加上-race标记 即可开启检测器运行上面的程序会给出清晰且信息量大的输出:该工具发现一处数据竞争,包含:一个goroutine在第20行对一 个变量进行写操作,跟着另一个goroutine在第22行对同一个变量进 行了未同步的读操作注意:竞争检测器只能发现在运行期确实发生的数据竞争(译注: 我也不太理解这话,请指导)8. Select 语句select语句是Go语言并发工具集中的终极工具select用于从一 组可能的通讯中选择一个进一步处理如果任意一个通讯都可以进一 步处理,则从中随机选择一个,执行对应的语句否则,如果又没有 默认分支(default case), select语句则会阻塞,直到其中一个通讯完 成。

      以下是一个玩具示例,演示select语句如何用于实现一个随机数 生成器:下面是相对更加实际一点的例子:如何使用select语句为一个操 作设置一个时间限制代码会输出变量news的值或者超时消息,具 体依赖于两个接收语句哪个先执行:函数time.After是Go语言标准库的一部分;它会在等待指定时 间后将当前的时间发送到返回的管道中9.综合所有示例 面的掌握这个程序演示了如何将管道用于被任意数量的goroutine发送和 接收数据,也演示了如何将select语句用于从多个通讯中选择一个示例输出:10.并行计算并发的一个应用是将一个大的计算切分成一些工作单元,调度到 不同的CPU上同时地计算将计算分布到多个CPU上更多是一门艺 术,而不是一门科学以下是一些经验法则:每个工作单元应该花费大约100微秒到1毫秒的时间用于计算 如果单元粒度太小,切分问题以及调度子问题的管理开销可能就会太 大如果单元粒度太大,整个计算也许不得不等待一个慢的工作项结 束这种缓慢可能因为多种原因而产生,比如:调度、其他进程的中 断或者糟糕的内存布局注意:工作单元的数目是不依赖于CPU的 数目的)尽可能减小共享的数据量。

      并发写操作的代价非常大,特别 是如果goroutine运行在不同的CPU上读操作之间的数据共享则通 常不会是个问题数据访问尽量利用良好的局部性如果数据能保持 在缓存中,数据加载和存储将会快得多得多,这对于写操作也格外地 重要下面的这个示例展示如何切分一个开销很大的计算并将其分布 在所有可用的CPU上进行计算先看一下有待优化的代码:思路很简单:确定合适大小的工作单元,然后在不同的goroutine中执行每个工作单元以下是并发版本的Convolve:你也许需要告诉运行时希望多少个goroutine来同时地运行代码。

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.