concurrency in go 中文版.pdf
《concurrency in go 中文版.pdf》由会员分享,可在线阅读,更多相关《concurrency in go 中文版.pdf(199页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、目录前序谁适合读这本书章节导读在线资源第一章并发编程介绍摩尔定律,可伸缩网络和我们所处的困境为什么并发编程如此困难数据竞争原子性内存访问同步死锁,活锁和锁的饥饿问题死锁活锁饥饿并发安全性优雅的面对复杂性第二章代码建模:序列化交互处理并发与并行什么是CSPCSP在Go中的衍生物Go的并发哲学第三章Go的并发构建模块Goroutinessync包WaitGroupMutex和RWMutexCondOncePoolChannelsselect语句GOMAXPROCS结论第四章Go的并发编程范式访问范围约束fo-select循环防止Goroutine泄漏or-channel错误处理-2-本文档使用看云
2、构建管道构建管道的最佳实践便利的生成器扇入扇出or-done-channeltee-channelbridge-channel队列context包小结第五章可伸缩并发设计错误传递超时和取消心跳请求并发复制处理速率限制Goroutines异常行为修复本章小结第六章Goroutines和Go运行时任务调度-3-本文档使用看云构建前序Go是门很棒的语言。当它首次被宣告给这个世界时,所包含的特性令人疯狂:语法简洁,编译速度非常快,执行效率高,支持鸭子类型,用它的并发原语处理问题非常直观。在第一次使用go关键字建立goroutine时(这就是本书将要介绍的东西),我太高兴了,与其他多种语言相比,我从未有
3、过如此爽快的感觉。多年来,我把Go代码一点一点加入个人项目中,直到有一天,猛然发觉已经积累了上万行之多。随着社区的不断发展,在Go中使用并发的最佳实践话题越来越多。有些开发者就他们的使用经验进行了讨论。但在社区中关于如何较好的进行并发操作,并没有一本完善的综合指南。正是基于这一点,我决定写这本书。以希望大家能够了解并掌握有关Go中并发性的高质量、全面的信息:如何使用它,如何将最佳实践和模式整合到系统中,以及它们如何在所有系统中运行。我尽力在这些考量之间取得平衡。衷心希望这本书能给你带来帮助!学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。前
4、序-4-本文档使用看云构建谁适合读这本书本书适用于有Go经验的开发人员;我不会试图解释语言的基本语法。关于如何在其他语言中呈现并发性的知识是有用的,但并非必需。在本书中,我们将讨论整个Go并发系统:常见的并发问题,Go并发设计背后的思考,Go并发原语的基本语法,常见的并发模式,以及各种工具,以帮助你应对日常工作中遇到的问题。书中介绍的主题非常广泛。你可以根据个人需求随时查看章节导读,以帮助浏览本书。谁适合读这本书-5-本文档使用看云构建章节导读在我阅读技术书籍时,通常会跳到那些能够激起我兴趣的部分,或者,如果我试图提高新技术的效率,我会疯狂地剔除与当前工作直接相关的部分。无论你的使用初衷是什么
5、,这里是本书的路线图,希望它能帮助你,引导你到需要的地方。第一章并发编程介绍本章介绍了并发的历史,阐述并发是一个重要的概念,并讨论了一些使并发难以正确的基本问题。同时简要介绍了Go如何缓解这些负担。如果你有相关的知识,或者只是想了解如何使用Go的并发原语技术,可以跳过本章。第二章代码建模:序列化交互处理本章介绍了Go的设计的一些初衷。这将有助于为你提供与Go社区中其他人交谈的背景信息,并帮助你了解为什么代码会按照的这样那样的思考方式运行。第三章Go的并发构建模块这一章我们将开始深入研究Go并发原语的语法。还会介绍标准库中的sync包,它负责处理Go的内存访问同步。如果你之前没有在Go中使用过并
6、发,但希望立即了解正式内容,那么这里就是开始的地方。在Go中编写并发代码的所需基础知识很少,在这里主要是将Go的概念与其他语言和并发模型进行比较。严格地说,没有必要理解这些东西,但这些概念可以帮助你全面了解Go语言的并发特性。第四章Go的并发编程范式在本章中,我们将着眼于如何把Go的并发原语组合在一起来形成有用的模式。这些模式既可以帮助我们解决问题,也可以避免在使用中常见的错误。如果你已经使用Go编写了一些并发代码,本章仍然是有用的。第五章大规模并发在本章中,我们将学习过的模式组合成更大的程序,展示服务和分布式系统中常用的经验。第六章Goroutines和Go运行时本章介绍Go运行时如何处理调
7、度goroutines。这对于那些想了解Go的运行时内部机制的人来说会很有意思。附录章节导读-6-本文档使用看云构建附录列举了各种工具和命令,可以帮助你更轻松地编写和调试并发程序。所有示例皆运行于main函数下,译者程序基于go1.10.1运行于windows7系统下,且全部验证无误章节导读-7-本文档使用看云构建在线资源Go有一个非常活跃和热情的社区!对于那些喜欢Go的人来说,很容易找到友善,乐于帮助的人。以下是我最喜欢的面向社区阅读资料,去获取帮助和同伴之间的互动吧:https:/golang.org/https:/golang.org/playhttps:/ data+4()5ifdat
8、a=06 fmt.Printf(thevalueis%v.n,data)71. 在Go中,可以使用go关键字同时运行一个函数。这样做创建了所谓的goroutine。在第3行和第5行都试图访问名为data的变量,但是并没有施行任何措施保证执行的顺序。运行此代码有三种可能的结果:没有输出。在这种情况下,第3行是在第5行之前执行的。输出thevalueis0。在这种情况下,第5行和第6行在第3行之前执行。输出thevalueis1。在这种情况下,第5行在第3行之前执行,但第3行在第6行之前执行。正如你所看到的,仅仅几行不确定的代码会在你的程序中引入巨大的变化。大多数情况下,数据竞争是由于开发人员按顺
9、序思考问题而引入的。他们认为,上一行代码会先于下一行代码执行。他们假设在if语句中读取数据变量之前,上面的goroutine将被调度并执行。在编写并发代码时,你必须仔细地遍历所有可能出现的场景。除非你正在使用本书稍后部分介绍的一些技巧,否则保证代码将按其在源代码中列出的顺序运行。我有时会发现在操作之间等待很长一段时间会很有帮助。想象一下,在调用goroutine的时间和运行的时间之间要经过一个小时。该程序的其余部分如何运作?如果在goroutine成功执行和程序到达if语句之间花了一个小时呢?以这种方式思考对我有所帮助,因为对于计算机而言,规模可能不同,但相对时间差异差不多。事实上一些开发者确
10、实这么干并发现看起来解决了并发上的问题,我们修改上个例子看看:1vardataint2gofunc()/13 data+4()5time.Sleep(1*time.Second)/这种做法实在太烂了!数据竞争-13-本文档使用看云构建6ifdata=07 fmt.Printf(thevalueis%v.n,data)8我们解决了数据竞争问题吗吗?没有。事实上,从这个方案中产生的所有三个结果仍然是可能的。我们在调用我们的goroutine和检查数据值之间的让程序休眠的时间越长,程序越接近实现正确性但这只是在概率上渐近地接近逻辑正确而已。除此之外,这样做已经在算法中引入了低效率。程序现在必须休眠一
11、秒钟才能使我们更有可能看不到的数据竞争。如果我们使用正确的方式来编写代码,我们可能无需等待,或者等待时间可能只有1微秒。这里要说的是,你应该总是以逻辑的正确性为目标。在你的代码中引入休眠可以是一种调试并发程序的方便方式,但不是解决方案。数据竞争的产生条件是最隐秘的并发错误类型之一,因为它们可能在代码投入生产后才会展现出来。它们通常是由代码执行环境发生变化或前所未有的突发事件引起的。在这些情况下,代码看起来行为正确,但实际上,这些操作按顺序执行的出现不确定性的几率非常高。学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。数据竞争-14-本文档使
12、用看云构建原子性当某种东西被认为是原子性的或者具有原子性的时候,这意味着在它运行的环境中,它是不可分割的或不可中断的。那么这到底意味着什么,为什么在使用并发代码时知道这很重要?第一件非常重要的事情就是了解“上下文(context)”这个词。在某个特定的上下文中,有的操作可能是原子的,有的可能不是。在你的流程环境中,原子状态的操作在操作系统环境中可能不是原子操作;在操作系统环境中是原子的操作在你的机器环境中可能不是原子的;并且在机器上下文中是原子的操作在应用程序上下文中可能不是原子的。换句话说,操作的原子性可以根据当前定义的范围而改变。清醒的认识到这个事实对你程序的利弊是非常重要的!在考虑原子性
13、时,经常需要做的第一件事是定义上下文或作用域,这个操作将被视为原子性的。这是思考程序的基础。2006年,游戏公司Blizzard成功起诉了MDYIndustries600万美元源于其开发的名为“滑翔机”的程序,该程序可在没有用户干预的情况下自动操作他们的游戏“魔兽世界”。这些类型的程序通常被称为“机器人外挂”。当时,魔兽世界有一个反作弊程序叫做“守望者“,它可以在你玩游戏的任何时候运行。除此之外,守望者将扫描主机的内存并运行启发式查找似乎用于作弊的程序。利用原子上下文的概念,滑翔机成功避免了守望者的这种检查。守望者认为扫描机器上的内存是一种原子操作,但在开始扫描之前,滑翔机利用硬件中断来隐藏自
14、己!守望者守护进程的内存扫描在进程的上下文中是原子的,但不在操作系统的上下文中。现在让我们看一下术语“不可分割”和“不可中断”。这些术语意味着在你定义的上下文中,原子的东西将在整个过程中发生,而不会同时发生任何事情。这让人有点糊涂,所以我们来看一个例子:i+这是一个任何人都可以明白的简单代码,但它很容易证明原子性的概念。它可能看起来很原子,但是一个简单的分析揭示了几种操作:检索i的值。增加i的价值。存储i的值。尽管这些操作中的每一个都是原子的,但三者的组合可能不是,取决于你的上下文。这揭示了原子操作的一个有趣特性:将它们结合并不一定会产生更大的原子操作。创建操作原子取决于你希望它在哪个上下文中
15、处于原子状态。如果你的上下文是一个没有并发进程的程序,那么这个代码在该上下文中是原子的。如果你的上下文是一个不会将i暴露给其他goroutine的goroutine,那么这个代码是原子的。原子性-15-本文档使用看云构建为什么我们如此在意原子性?原子性非常重要,因为如果说某些东西是原子的,那么就隐式地意味着在并发环境中是安全的。这使我们能够编写逻辑上正确的程序,并且我们稍后将看到甚至可以用作优化并发程序的一种方式。大多数语句不是原子的,更不用说函数,方法和程序了。如果原子性是组成逻辑上正确的程序关键,并且大多数语句不是原子的,那么我们如何调和这两个问题?稍后我们会深入探讨,但总之,我们可以通过
16、采用各种技术来强制原子性。至于如何决定你的代码的哪些部分需要是原子的,以及需要划分到什么样的粒度,我们在下一节继续讨论。学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。原子性-16-本文档使用看云构建内存访问同步假设我们有一个数据竞争:两个并发进程试图访问同一个内存区域,并且它们访问内存的方式不是原子的。我们对之前的例子进行一些修改:vardataintgofunc()data+()ifdata=0fmt.Println(thevalueis0.)elsefmt.Printf(thevalueis%v.n,data)我们在这里添加了一个el
17、se子句,以便不管数据的值如何,总会得到一些输出。请记住,正如它所写的那样,存在数据竞争,并且程序的输出将完全不确定。程序中有一些操作需要独占访问共享资源。在这个例子中,我们找到三处:goroutine正在增加数据变量。if语句,它检查数据的值是否为0。fmt.Printf语句,用于检索输出数据的值。有很多方法可以保护这些访问,Go有很好的方式来处理这个问题,解决这个问题的方法之一是让这些操作同步访问内存。让我们看看该怎样做到这一点。下面的代码不是Go的惯用法(我不建议你像这样解决数据竞争问题),但它很简单地演示了内存访问同步。如果这个例子中的任何类型,函数或方法对你来说都很陌生,那没问题。跟
18、踪注释,关注同步访问内存的概念就行。varmemoryAccesssync.Mutex/1varvalueintgofunc()memoryAccess.Lock()/2value+memoryAccess.Unlock()/3()memoryAccess.Lock()/4ifvalue=0fmt.Printf(thevalueis%v.n,value)elsefmt.Printf(thevalueis%v.n,value)memoryAccess.Unlock()/5内存访问同步-17-本文档使用看云构建1. 这里我们添加一个变量,它允许我们的代码同步对数据变量内存的访问。第三章的sync包
19、会介绍sync.Mutex类型的细节。2. 在这里我们声明,除非解锁,否则我们的goroutine应该独占访问此内存。3. 在这里,我们声明这个对该内存的访问已经完成了。4. 在这里,我们再次声明接下来的条件语句应该独占访问数据变量的内存。5. 在这里,我们声明对内存的访问已经完成。在这个例子中,我们为开发者制定了一个约定。任何时候开发人员都想访问data变量的内存,必须首先调用Lock,当完成访问操作时,必须调用Unlock。这两个语句之间的代码可以假定它拥有对数据的独占访问权;我们已经成功地同步了对内存的访问。注意,如果开发者不遵循这个约定,我们就没有保证独占访问的权利!你可能已经注意到,
20、虽然我们已经解决了数据竞争,但我们并没有真正解决竞争条件!这个程序的操作顺序仍然不确定。我们刚刚只是缩小了非确定性的范围。在这个例子中,仍然不确定goroutine是否会先执行,或者我们的if和else块是否都会执行。稍后,我们将探索正确解决这类问题的工具。从表面上看,这似乎很简单:如果你发现你有这样的需求,添加点来同步访问内存!很简单,对吧?但是!确实,你可以通过同步访问内存来解决一些问题,但正如我们刚刚看到的,它不会自动解决数据竞争或逻辑正确性问题。此外,它还可能导致维护和性能问题。请注意,之前我们提到已经创建了一个声明需要对某些内存进行独占访问的约定。约定本身是没问题的,但实际开发中我们
21、总是会丢三落四。更别说开发组里总会有这样或那样不遵守约定的人。值得庆幸的是,随后我们还将介绍一些更有效的方法。以这种方式同步对内存的访问会导致性能下降。每次我们执行其中一项操作时,程序会暂停一段时间。这带来了两个问题:加锁的程序部分是否重复进入和退出?加锁的程序对内存占用到底有多大?要说清这两个问题简直是门艺术。同步对内存的访问也与其他并发建模存在关联,我们将在下一节讨论这些问题。学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。内存访问同步-18-本文档使用看云构建死锁,活锁和锁的饥饿问题前面的章节都是关于程序正确性的讨论,如果这些问题得到
22、正确处理,程序永远不会给出错误的答案。不幸的是,即使你成功处理了这些类别的问题,还有另一类问题需要解决:死锁,活锁和饥饿。这些问题都涉及确保您的程序在任何时候都能够有效执行。如果处理不当,您的程序可能会进入某个状态中,最终停止运行。学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。死锁,活锁和锁的饥饿问题-19-本文档使用看云构建死锁死锁是所有并发进程都在彼此等待的状态。在这种情况下,如果没有外部干预,程序将永远不会恢复。如果这听起来很严峻,那是因为它确实很严峻!Go运行时会检测到一些死锁(所有的例程必须被阻塞或“休眠”),但这对于帮助你防止
23、死锁产生没有多大帮助。为了帮助你更直观的认识死锁,我们先来看一个例子。同样的,跟着注释走,任何变量、函数、语句都不重要:typevaluestructmusync.Mutexvalueintvarwgsync.WaitGroupprintSum:=func(v1,v2*value)deferwg.Done()v1.mu.Lock()/1deferv1.mu.Unlock()/2time.Sleep(2*time.Second)/3v2.mu.Lock()deferv2.mu.Unlock()fmt.Printf(sum=%vn,v1.value+v2.value)vara,bvaluewg.A
24、dd(2)goprintSum(&a,&b)goprintSum(&b,&a)wg.Wait()1. 这里我们试图访问带锁的部分2. 这里我们试图调用defer关键字释放锁3. 这里我们添加休眠时间以造成死锁如果你试着运行这段程序,应该会看到这样的输出:fatalerror:allgoroutinesareasleep-deadlock!为什么?如果仔细观察,你将在此代码中看到计时问题。下面的时序图能清晰的展现问题所在:死锁,活锁和锁的饥饿问题-20-本文档使用看云构建实质上,我们创建了两个不能一起运转的齿轮:我们的第一个打印总和调用a锁定,然后尝试锁定b,但与此同时,我们打印总和的第二个调用
25、锁定了b并尝试锁定a。两个goroutine都无限地等待着彼此。为了保持这个例子简单,我使用time.Sleep来触发死锁。但是,这引入了竞争条件!你能找到它吗?一个逻辑上“完美”的死锁将需要正确的同步。这似乎很明显,为什么当我们以这种方式绘制图表时出现这种僵局,但我们会从更严格的定义中受益。事实证明,出现僵局时必定存在一些条件,1971年,埃德加科夫曼在一篇论文中列举了这些条件。这些条件现在称为科夫曼条件,是帮助检测,防止和纠正死锁的技术基础。科夫曼条件如下:相互排斥并发进程在任何时候都拥有资源的独占权。等待条件并发进程必须同时持有资源并等待额外的资源。没有抢占并发进程持有的资源只能由该进程
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- concurrency in go 中文版
限制150内