时下,golang语言已经变成了最热门的编程语言之一。借助与他高效、静态类型、以及强大的并发性,以及不乏动态语言特性的语法,吸引了大量C、C++、Java、python码农成为他的拥趸。尤其是在容器领域,golang开发的docker更是一枝独秀,风靡于零。今天虫虫就给大家介绍一下golang中最值得称道的并发功能,介绍常见的Go并发模式,本文假设你已经熟悉golang基本语法,有初步的golang编程经验。
我们首先要说的是,任何的并发性,即使使用Go内置的最简单的原语,本质上也很复杂。所以建议Golang初学者尽可能多,多学多用其并发(goroutines和channel),直到熟练掌握该技能。
Golang并发使用建议
最小化并发接口
我们假想,将并发视看成一个Web网页。我们不能让(或至少不应该)ORM(处理数据请求,返回数据)给你返回HTML片段。接口应该以直接的方式编写:参数进来,返回值出来。易于测试,易于推理。绝大多数代码不应与信道(channel)交互或通过协程(goroutine)运行。代码也不应该返回表示延迟或正在进行的工作的回调(callback)。
避免过早优化并发
Go社区喜欢优化,而且很多是不必要的优化。尽管过度优化一般情况都是无害的,但是涉及信道和协程时候,除非有明显的性能改进,否则绝对应该避免过度优化。一个具体的例子是创建任务池时候,使用协程方便快捷,即用即建,不用既弃。为具体小任务创建协程,不要创建长期工作的协程。需要指出的是,即便你调度得当,基于CPU限制,你的协程都可能成为整个程序的瓶颈,如果你确定需要写一个函数,最好给其添加runtime.Gosched()(有地方用time.Sleep(0)))。
利用内置线程安全性
在golang中有一些常用的功能可以安全地在并发中使用。比如写文件,至少在Linx中将信道映射到协程然后写入标准输出(标准错误也类似)是可以的。但因为在文件写方法中文件已经被锁定,这样做是多余的。
并发模式
下面我们介绍一下常见的简单可靠的并发模式。
安全完成
任何使用协程的人都应该非常清楚这一点,协程并不表示完全开箱即用。有几种方法可以在完成时构建阻塞,但是对该模式,使用sync.WaitGroup类型。通常是你在开始和完成协程之前添加waitgroup。下面的第一个例子将展示它是如何工作的。重要的是要记住,如果你是在协程内部添加的话,你可能犯了一个错误。
并发输出
这几乎不能称之为模式,但对于任何并发代码来说都是一个良好的开端:
在上面的代码中,我们使用WaitGroup来阻塞,直到所有未完成的协程都完成。上面使用的另一种模式是令牌通道,用于避免同时运行10个以上的协程。如前所述,这意味着我们不需要一个10 worker的任务池,而是最多运行10个协程。这意味着,当我们加快工作或完成最后剩余的工作时,我们只有那么多的协程,而不是有那么多等待工作的空闲协程。
这个模式的好处是输出会在它准备就绪时出现,这在Unix管道中很好用,或者只是让用户意识到工作是以自然的方式完成的(而不是微调器或类似的东西)主要缺点是输出的顺序几乎完全与底层函数的速度无关。
请特别注意延迟处理协程中的标记和等待组。在使用早期返回添加错误处理时,应该切换到延迟。否则程序将永远挂起。
并发映射
最后是一个更取巧的模式,在 Golang中也随处可见。与上面的模式类似,但每个协程映射到切片内的一个槽。切片可以安全地并行修改。
与并发输出不同,此代码仅在所有数据准备就绪时打印输出,但是对其维护也以相对比较自然。
最后,关于Golang,不管你是否用得到,是一个每一个人都值得尝试的语言,如果你想学习,虫虫在此给你推它荐两本书,一本是go语言圣经《The Go Programming Language》,它不仅仅是一本伟大的Go语言教程,而且是一本优秀的编程书,里面有大量的并发性技巧介绍。
另一本要考虑学习Go的书是Go Programming Blueprints。它具有几乎交互式的风格,可以在其中编写代码,查看语法错误(或其他任何内容),修复代码并进行迭代。
最后请关注虫虫,虫虫会时常推送编程技巧、有用的工具、业界动态等相关内容给大家奉上。
领取专属 10元无门槛券
私享最新 技术干货