首页 > 编程笔记 > Go语言笔记

Go语言并发简介(并发与并行的区别)

Go 语言中的并发是指能让某个函数独立于其他函数运行的能力。

当一个函数创建为 goroutine(协程)时,Go语言会将其视为一个独立的工作单元,这个单元会被调度到可用的逻辑处理器上执行。

并发与并行

并发是指在同一时间段内执行多个任务。并行是指在同一时刻执行多个任务。

并行就是在任一粒度的时间内都具备同时执行的能力:最简单的并行就是多机,多台机器并行处理;SMP 表面上看是并行的,但由于是共享内存、线程间的同步等,不可能完全做到并行。

并发是在规定的时间内多个请求都得到执行和处理,强调的是给外界的感觉,实际上内部可能是分时操作的。并发重在避免阻塞,使程序不会因为一个阻塞而停止处理。并发典型的应用场景是分时操作系统。

并行是硬件和操作系统开发者重点考虑的问题,作为应用层的程序员,唯一可以选择的就是充分借助操作系统提供的 API 和程序语言特性,结合实际需求设计出具有良好并发结构的程序,提升程序的并发处理能力。现代操作系统能够提供的最基础的并发模型就是多线程和多进程。

在当前的计算机体系下,并行具有瞬时性,并发具有过程性;并发在于结构,并行在于执行。应用程序具备好的并发结构,操作系统才能更好地利用硬件并行执行,同时避免阻塞等待,合理地进行调度,提升 CPU 利用率。应用层程序员提升程序并发处理能力的一个重要手段,就是为程序设计良好的并发结构。

指定使用核心数

Go 语言会默认使用 CPU 的核心数,这些功能都是自动调整的,但是也提供了相应的标准库来指定核心数。使用 flag 包可以调整程序运行时调用的 CPU 核心数。

例如:
var numCores = flag. Int("n", 2, "CPU核心数")
in main()
flag.Pars()
runtime.GOMAXPROCS(*numCores)
协程可以通过调用 runtime.Goexit() 来停止,尽管这样做几乎没有必要。
package main
import (
   "fmt"
   "time"
)
func main() {
   fmt.printin("这里是main()开始的地方")
   go longWait()
   go shortwait()
   fmt.Println("挂起main()")
   //挂起工作时间以纳秒(ns)为单位
   time.Sleep(10 * 1e9)
   fmt.Println("这里是main()结束的地方")
}
func longWait() {
   fmt.Println("开始longWait()")
   time.Sleep(5 * 1e9)    //等待5秒
   fmt.Println("结束longWait()")
}
func shortWait() {
   fmt.Println("开始shortWait()" )
   time.Sleep(2 * 1e9)     //等待2秒
   fmt.Println("结束shortwait()")
}
运行结果为:

这里是main()结束的地方
挂起main()
开始shortWait()
开始longWait()
结束shortwait()
结束longWait()
这里是main()结束的地方

main()、longWait() 和 shortWait() 这 3 个函数作为独立的处理单元按顺序启动,然后开始并行运行,每个函数都在运行的开始和结束阶段输出了消息。为了模拟它们运算的时间消耗,使用了 time 包中的 Sleep() 函数。Sleep() 可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(ns)。

程序按照我们期望的顺序打印出了消息,几乎都一样,可是我们明白这是模拟出来的,并且是并行的方式。让 main() 函数暂停 10 秒从而确定它会在另外两个协程之后结束。如果不这样(如果让 main() 函数停止 4 秒),main() 会提前结束,longWait() 则无法完成。如果不在 main() 中等待,协程会随着程序的结束而消亡。

当 main() 函数返回的时候,程序退出,它不会等待任何其他非 main 协程的结束。这就是为什么在服务器程序中,每一个请求都会启动一个协程来处理,server() 函数必须保持运行状态。通常使用一个无限循环来达到这样的目的。

另外,协程是独立的处理单元,一旦陆续启动一些协程,就无法确定它们是什么时候真正开始执行的。代码逻辑必须独立于协程调用的顺序。

并发与并行的区别

并发和并行的根本区别是任务是否同时执行。

并发不是并行。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情。

在很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情。这种“使用较少的资源做更多的事情”的哲学,也是指导 Go 语言设计的哲学。

如果希望让 goroutine 并行,必须使用多于一个逻辑处理器。当有多个逻辑处理器时,调度器会将 goroutine 平等分配到每个逻辑处理器上。这会让 goroutine 在不同的线程上运行。

不过要想真的实现并行的效果,用户需要让自己的程序运行在有多个物理处理器的机器上。否则,哪怕 Go 语言运行时使用多个线程,goroutine 依然会在同一个物理处理器上并发运行,达不到并行的效果。

相关文章