原始链接
1.进行例行理解
Go语言的最大功能是从语言级别支持并发(Goroutine)。 Goroutine是Go中最基本的执行单元。实际上,每个Go程序至少都有一个Goroutine:主Goroutine。程序启动时,将自动创建。
为了更好地了解Goroutine,让我们谈谈线程和协程的概念
线程(Thread):有时称为轻量级进程(LWP),它是程序执行流程的最小单元。标准线程由线程ID,当前指令指针(PC),寄存器集和堆栈组成。此外,线程是流程中的实体,并且是系统独立调度和调度的基本单元。线程本身并不拥有系统资源,而是仅在运行中具有一些必不可少的资源,但是可以与属于同一进程的其他进程组合。线程共享该进程拥有的所有资源。
线程具有自己的独立堆栈和共享堆,共享堆而不是共享堆栈,并且线程切换通常由操作系统安排。
协程:也称为微线程。像子例程(或函数)一样,协程也是程序的组成部分。与子例程相比,协程更通用,更灵活,但在实践中并未广泛使用。
类似于线程,堆是共享的,栈不是共享的,协程的切换通常由程序员在代码中显式控制。它避免了上下文切换的额外成本,并考虑了多线程的优点,并简化了高并发程序的复杂性。
其他语言中的Goroutine和协程在用法上相似,但字面意义不同(一个是Goroutine,另一个是协程),协程是一种协作任务控制机制。简单来说,协程不是并发的,而Goroutine支持并发。因此Goroutine可以理解为Go语言的协程。同时,它可以在一个或多个线程上运行。
首先举一个简单的例子
func loop() {
for i := 0; i < ; i++ {
fmt.Printf("%d ", i)
}
}
func main() {
go loop() // 启动一个goroutine
loop()
}
2. GO并发的实现原理2. 1、 Go并发模型
Go实现两种形式的并发。第一个是众所周知的:多线程共享内存。实际上,它是Java或C ++等语言的多线程开发。另一个是Go语言所独有的,并由Go语言推荐:CSP(通信顺序过程)并发模型。
CSP并发模型是在1970年左右提出的一个概念。它是一个相对较新的概念。与通过共享内存的传统多线程通信不同,CSP强调“通过通信的方式共享内存”。
请记住以下句子:
请勿通过共享内存进行通讯;快点,通过交流分享记忆。
“相反,不要使用共享内存进行通信,而是通过通信共享内存。”
诸如Java,C ++或Python之类的普通线程并发模型通过共享内存程之间进行通信。一种非常典型的方法是通过锁访问共享数据(例如数组,Map或某个结构或对象)。因此,在许多情况下,派生了一个方便的数据结构,称为“线程安全数据结构”。例如,Java提供的包“ java.util.concurrent”中的数据结构。 Go中也实现了传统的线程并发模型。
Go的CSP并发模型是通过goroutine和渠道实现的。
生成goroutine的方法非常简单:运行Go,它将生成它。
go f();
通讯机制通道也很方便。该通道用于传输数据和获取数据。
在通信过程中,数据传输通道和数据获取将不可避免地成对出现,因为这两个goroutine仅当在此处传输并带到那里时才相互通信。
此外,无论通过还是采用,都必须将其阻塞,直到另一个goroutine通过或采用。
示例如下:
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}
请注意,main()本身也运行一个goroutine。
消息:= make(chan int)这声明了阻塞的未缓冲通道
chan是一个关键字,这意味着我要创建一个频道
2. 2 GO并发模型的实现原理
让我们从线程开始。无论在语言级别上使用哪种并发模型,在操作系统级别上都必须以线程形式存在。根据不同的资源访问权限,可以将操作系统分为用户空间和内核空间。内核空间主要用于访问硬件资源,例如CPU资源,I / O资源和内存资源,并为上层应用程序提供最基本的基本资源。用户空间是上层应用程序的固定活动空间。用户空间无法直接访问资源。它必须使用“系统调用”,“库函数”或“ Shell脚本”来调用内核空间提供的资源。
从狭义上讲,我们当前的计算机语言可以被视为一种“软件”。它们中的所谓“线程”通常是用户模式线程和操作系统自己的内核模式线程(简称KSE)。差异。
线程模型的实现可以分为以下几种方式:
2. 3. 1个用户级线程模型
截屏2020-01-12 1 8. 1 5. 1 7. png
如图所示,多个用户模式线程对应于一个内核线程,并且程序线程的创建,终止,切换或同步必须自己完成。它可以进行快速的上下文切换。缺点是无法有效使用多核CPU。
2. 3. 2内核级线程模型
内核级别1:1
此模型直接调用操作系统的内核线程。所有线程的创建,终止,切换和同步都由内核完成。用户模式线程对应于系统线程。它可以使用多核机制,但是上下文切换需要额外的资源。 C ++就是这样。
2. 3. 3两级线程模型
二级M:N

M:N模型
此模型是用户级线程模型和内核级线程模型之间的线程模型。该模型的实现非常复杂。与内核级线程模型相似,一个进程可以对应于多个内核级线程,但是进程中的线程与内核线程并不一一对应。此线程模型首先创建多个内核级线程,然后该线程使用其自己的用户级线程来对应于创建的多个内核级线程。用户级线程需要由自己的程序进行调度,并将内核级线程移交给操作系统内核进行调度。
M个用户线程对应于N个系统线程,缺点增加了实现调度程序的难度。
Go语言的线程模型是一种特殊的两级线程模型(GPM调度模型)。
3. Go线程实现模型MPG
M表示机器,并且M与内核线程直接关联。由操作系统管理。
P表示“处理器”,它表示M所需的上下文,并且也是处理用户级代码逻辑的处理器。它负责连接M和G的调度上下文,并连接等待的G和M。
G表示Goroutine,它本质上是轻量级线程。包括呼叫堆栈,重要的调度信息,例如通道。
P的数量由环境变量中的GOMAXPROCS确定。一般来说,它对应于内核数。例如,在4Core服务器上启动了四个线程。将有许多G,每个P将从准备好的队列中弹出Goroutine。为了减少锁竞争,通常每个P将负责一个队列。
这三个之间的关系如下图所示:
三方之间的关系
宏观关系图
上图是关于两个线程(内核线程)的。 M对应于内核线程,M也连接到上下文P,上下文P等效于“处理器”,并且上下文连接到一个或多个Goroutine。为了运行goroutine,线程必须保存上下文。
上下文P(处理器)的数量在启动时或通过运行时函数GOMAXPROCS()设置为GOMAXPROCS环境变量的值。通常,它在程序执行期间不会改变。固定数量的上下文意味着在任何时候都只有固定数量的线程运行Go代码。我们可以使用它来调整从Go进程到个人计算机的呼叫。例如,四核PC在4个线程上运行Go代码。
图中P所执行的Goroutine为蓝色;处于待执行状态的Goroutine为灰色,灰色的Goroutine形成一个运行队列队列。
在Go语言中,很容易启动goroutine:go函数很好,因此每次执行go语句时,运行队列都会在末尾添加一个goroutine。一旦上下文运行goroutine到调度点,它将从其运行队列开始。弹出goroutine,设置堆栈和指令指针,然后开始运行goroutine。
放弃P(处理器)
您可能在想,为什么需要上下文?我们可以直接删除上下文并让Goroutine的运行队列挂在M上吗?答案是不。上下文的目的是允许我们在内核线程被阻塞时直接释放其他线程。
一个非常简单的示例是系统调用sysall。线程不得同时执行代码,并且系统调用将被阻止。这时,该线程M需要放弃当前上下文P,以便可以安排其他Goroutine进行执行。

系统调用
如上图左图所示,M0中的G0执行系统调用,然后创建一个M1(也可能来自线程缓存)(转到右图),然后M0丢弃P,等待返回值系统调用,M1接受P,继续执行Goroutine队列中的其他Goroutine。
当系统调用syscall结束时,M0将“窃取”上下文。如果不成功,则M0将其Gouroutine G0放在全局运行队列中,将其自身放入线程缓存中并进入睡眠状态。全局运行队列是每个P在运行其本地Goroutine运行队列之后用来拉出新的goroutine的位置。 P还将定期检查全局运行队列上的goroutine,否则,可能不会执行全局运行队列上的goroutine并将其饿死。
工作分配均衡
根据上述声明,上下文P将定期检查全局goroutine队列中的goroutine,以便在使用其自己的Goroutine队列时可以做一些事情。如果全局goroutine队列中的goroutine消失了怎么办?只是从其他正在运行的P的运行队列中窃取。
每个P中的不同Goroutine导致不同的工作效率和时间。在具有大量P和M的环境中,您不能让P运行自己的Goroutine,并且什么也不能做,因为其他P可能要运行很长的goroutine队列,并且需要保持平衡。
如何解决?
Go的方法很简单,窃取了其他P的一半!
余额分配g
Goroutine摘要
优点:
1、开销很小
Posix的线程API可以提供丰富的API,例如配置自己的CPU亲和力,申请资源等,尽管线程获得了与进程相同的控制权,但开销也非常大。在Goroutine中,由于不需要这些额外的开销,因此Golang程序可以支持10w级Goroutine。
默认情况下,每个goroutine(协程)占用的内存都比Java和C线程少得多(goroutine:2KB,线程:8MB)
2、良好的调度性能
在Golang程序中,操作系统级别的线程调度通常不会做出适当的调度决策。例如,在GC中,内存必须达到一致的状态。在Goroutine机制中,Golang可以控制Goroutine的调度以在适当的时间执行GC。
在应用程序层进行线程模拟,避免了上下文切换的额外成本,并考虑了多线程的优点。简化了高度并发程序的复杂性。
缺点:
协程调度机制无法实现公平调度。
参考:
链接
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-360718-1.html
就是嘛