GO并发
使用goroutine运行程序,检测并修正状态,利用通道共享数据。
通常程序会被编写为一个顺序执行并完成一个独立任务的代码。
web服务器需要在各自独立的套接字(Socket)上通过接受多个数据请求。
每个套接字请求是独立的,独立于其他套接字进行处理。并行执行多个请求的能力可显著提高这类系统的性能。
Go语言中的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为goroutine时,Go会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。
Go语言运行时的调度器,管理创建的所有goroutine并为其分配执行时间。
这个调度器在操作系统之上,将操作系统的线程与语言运行时的逻辑处理器绑定,并在逻辑处理器上运行
goroutine。调度器在任何给定的时间,都会全面控制哪个goroutine 要在哪个逻辑处理器上运行。
Go语言的并发同步模型来自一个叫作通信顺序进程(Communicating Sequential Processes,CSP)
的范型(paradigm)。CSP 是一种消息传递模型,通过在goroutine 之间传递数据来传递消息,而不是
对数据进行加锁来实现同步访问。用于在goroutine 之间同步和传递数据的关键数据类型叫作通道
(channel)。
使用通道可以使编写并发程序更容易,也能够让并发程序出错更少。
什么是操作系统的线程(thread)和进程(process)
便于理解Go语言运行时调度器如何利用操作系统来并发运行goroutine。
当运行一个应用程序的时候,操作系统会为这个应用程序启动一个进程。可以将这个进程看作一个包含了应用程序在运行中需要用到和维护的各种资源的容器。
一个线程是一个执行空间,这个空间会被操作系统调度来运行函数中所写的代码。
每个进程至少包含一个线程,每个进程的初始线程被称作主线程。
因为执行这个线程的空间是应用程序的本身的空间,所以当主线程终止时,应用程序也会终止。
操作系统将线程调度到某个处理器上运行,这个处理器并不一定是进程所在的处理器。
不同操作系统使用的线程调度算法一般都不一样,但是这种不同会被操作系统屏蔽,并不会展示给程序员。
进程维护了应用程序运行时的内存地址空间、文件和设备的句柄以及线程。操作系统的调度器决定哪个线程会获得给定的CPU的运行时间。
操作系统会在物理处理器上调度线程来运行,而Go 语言的运行时会在逻辑处理器上调度goroutine来运行。
每个逻辑处理器都分别绑定到单个操作系统线程。操作系统线程、逻辑处理器和本地运行队列之间的关系。
如果创建一个goroutine 并准备运行,这个goroutine 就会被放到调度器的全局运行队列中。之后,调度器就
将这些队列中的goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列上,Go语言的
运行时默认会为每个可用的物理处理器分配一个逻辑处理器。
本地运行队列中的goroutine会一直等待直到自己被分配的逻辑处理器执行。
go调度器如何管理goroutine:
Go语言运行时会把goroutine调度到逻辑处理器上运行。这个逻辑处理器绑定到唯一的操作系统线程。当goroutine可以运行的时候,会被放入逻辑处理器的执行队列中。
当goroutine执行了一个阻塞的系统调用时,调度器会将这个线程与处理器分类,并创建一个新线程来运行这个处理器上提供的服务。
有时,正在运行的goroutine 需要执行一个阻塞的系统调用,如打开一个文件。当这类调用
发生时,线程和goroutine 会从逻辑处理器上分离,该线程会继续阻塞,等待系统调用的返回。
与此同时,这个逻辑处理器就失去了用来运行的线程。所以,调度器会创建一个新线程,并将其
绑定到该逻辑处理器上。之后,调度器会从本地运行队列里选择另一个goroutine 来运行。一旦
被阻塞的系统调用执行完成并返回,对应的goroutine 会放回到本地运行队列,而之前的线程会
保存好,以便之后可以继续使用。
如果一个 goroutine 需要做一个网络I/O 调用,流程上会有些不一样。在这种情况下,goroutine
会和逻辑处理器分离,并移到集成了网络轮询器的运行时。一旦该轮询器指示某个网络读或者写
操作已经就绪,对应的goroutine 就会重新分配到逻辑处理器上来完成操作。调度器对可以创建
的逻辑处理器的数量没有限制,但语言运行时默认限制每个程序最多创建10 000 个线程。这个
限制值可以通过调用runtime/debug 包的SetMaxThreads 方法来更改。如果程序试图使用
更多的线程,就会崩溃。
并发(concurrency)不是并行(parallelism)。并行是让不同的代码片段同时在不同的物理处
理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做
了一半就被暂停去做别的事情了。在很多情况下,并发的效果比并行好,因为操作系统和硬件的
总资源一般很少,但能支持系统同时做很多事情。这种“使用较少的资源做更多的事情”的哲学,
也是指导Go 语言设计的哲学。
如果希望让goroutine 并行,必须使用多于一个逻辑处理器。当有多个逻辑处理器时,调度器
会将goroutine 平等分配到每个逻辑处理器上。这会让goroutine 在不同的线程上运行。不过要想真
的实现并行的效果,用户需要让自己的程序运行在有多个物理处理器的机器上。否则,哪怕Go 语
言运行时使用多个线程,goroutine 依然会在同一个物理处理器上并发运行,达不到并行的效果。
在一个逻辑处理器上并发运行goroutine 和在两个逻辑处理器上并行运行两个并
发的goroutine 之间的区别。