本文收录在专栏Go系列中,更多相关文章可点击前方链接阅读。
并发
并发在程序运行中,一直是个效果显著的性能提升手段。从逻辑上来看,并发就是把一个大任务分成几个小任务同时进行。
由此我们可以看出并发带来的两大优势:
-
更高效地运行大型任务。
-
让机器可以同时运行不同类型的任务。
进程与线程
并发的概念为计算机带来了两个伟大的发明:进程与线程
我们日常使用电脑时会同时开多个程序,如浏览器、微信等,这些都是并发的体现。此时我们会遇到一个问题:如何确保不同程序之间的数据不会互相影响?
开发者们为此设计了一套完整的运行规则。操作系统会为每个运行的程序创建一个空间,这个程序运行需要的所有资源包括内存地址空间、文件句柄、设备和线程等。都会被限制在这个空间里,不会相互影响。而这个空间就是进程。
进程是操作系统层面的内容,它可以支持操作系统同时运行多个程序应用。但当执行的程序是个庞大的应用时,我们能不能继续发挥并发的优势,提高运行效率呢?
这就引出了线程。线程是属于应用层面的设计,理念与进程相似,但线程的重点是通过并发的方式服务好一个应用。
实际上进程与线程都是有操作成本的,应用可以利用进程或者线程实现很多复杂的业务,但同时会带来大量复杂度,如进线程之间的通信。其中,由于线程更靠近应用,应用在利用线程时,成本会比进程低。
Go中的并发——Goroutine
并发是 Go 的一大亮点,也是它在部分场景下拥有强大性能的原因。原因是 Go 在应用内部实现了一套并发的设计—— Gorountine 。Goroutine 是运行在应用内部的,可以理解为 Go 用应用实现了一套“线程”。这种设计也被称为 “协程” 。
Go 语言层面支持的 go 关键字,可以快速的让一个函数创建为 goroutine,我们可以认为 main 函数就是作为 goroutine 执行的。操作系统调度线程在可用处理器上运行,Go运行时调度 goroutine 在绑定到单个操作系统线程的逻辑处理器中运行。即使使用这个单一的逻辑处理器和操作系统线程,也可以调度数十万 goroutine 以惊人的效率和性能并发运行。
实现这么一套机制的难度可想而知,而 Go 不仅做到了,而且做到很好,这就是它在开发语言界占有如此分量的原因。
相比传统操作系统线程来说,goroutine 的优势主要 是:
- 资源占用小,每个 goroutine 的初始栈大小仅为 2k;
- 由 Go 运行时而不是操作系统调度,goroutine 上下文切换在用户层完成,开销更小;
- 在语言层面而不是通过标准库提供。 goroutine 由 go 关键字创建,一退出就会被回收或 销毁,开发体验更佳;
- 语言内置 channel 作为 goroutine 间通信原语, 为并发设计提供了强大支撑。
并发不是并行
最后需要强调一点,虽然我们常说并发可以让机器“同时”运行多个任务。但这里的“同时”,并不是严格意义上的同一时间。而是充分利用了 CPU 的运行时间空隙,不同地执行不同的任务。
因此,并发并不是并行。
当然,在进入了多核 CPU 时代之后,严格意义的并行是有可能的。当 CPU 不同的核在同时处理不同任务时,此时就是一个并行的状态。
总结
今天,我们了解了计算器的并发概念。并了解其中两个重要的实现设计:进程与线程。随后我们又看了 Go 中自己设计的 Goroutine 。Go 的并发之所以这么有名,原因在于它优秀的应用级设计。有了 Go 的鼓励,相信将来此类的实现方案会越来越多。
如果你觉得本文对你有一点帮助,麻烦给我点个赞吧~~ 谢谢