教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Go的隐秘世界:一个Goroutine要几个Thread

Go的隐秘世界:一个Goroutine要几个Thread

发布时间:2022-03-02   编辑:jiaochengji.com
教程集为您提供Go的隐秘世界:一个Goroutine要几个Thread等资源,欢迎您收藏本站,我们将为您提供最新的Go的隐秘世界:一个Goroutine要几个Thread资源

书接上文:Go的隐秘世界:有Thread为啥还要Goroutine

本文从一个小问题开始:如果一个Go程序只有一个goroutine(没有用 go keyword 启动其他 goroutines),那么 Go runtime 会启动几个线程来执行这个程序呢?

大家可能会说”一个“!因为 Go 的教程说它可以用寥寥几个 threads 执行几万个 goroutines。而且类比 thread 的启动方式 —— 一个进程启动时只有一个 thread,其他 thread 都是从这个 thread 分支出来的。

真的是这样吗?且看下面程序 a.go。

<pre><code class="lang-go hljs"><span class="kn">package</span> <span class="nx">main</span> <span class="c1">// import "C" </span><span class="c1"/><span class="kn">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"os"</span> <span class="s">"time"</span> <span class="p">)</span> <span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nf">Getpid</span><span class="p">())</span> <span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">1000</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span> <span class="p">}</span> </code></code></pre>

我在一个terminal里运行 go run a.go。它会打印自己的 pid,然后进入很长时间的等待,使我有时间在另一terminal里用 ps M <pid> 命令查看这个进程有几个线程。在我的有两个 CPU 的 Ubuntu Linux VM(其实是 macOS 上的 Docker container)里,ps M 命令说这个进程启动了 5 个线程!

怎么会有这么多线程?因为 goroutine 的启动模式和 thread 不一样 —— 每个 Go 程序一开始就会启动多个 goroutines。

这里还是没有提及 Cgo。Cgo 对 goroutine 的启动和调度由什么影响呢?先说启动。

如果我们在上面代码里加一行 import "C",也就是说”准备用 Cgo 啦“。重复上述实验。ps M 命令说线程数量变成了 6!增加一行 import "C",就导致线程数量增加了一个!

这个线程是 Go runtime 里的入口函数 main 启动的:<span class="invisible">https://</span><span class="visible">github.com/golang/go/bl</span><span class="invisible">ob/release-branch.go1.15/src/runtime/proc.go#L170-L189</span><span class="ellipsis"/> 。我用的是 Go 1.15 做的上述实验,这个源码连接也指向 Go 1.15 的源码。具体的说,是 startTemplateThread 函数启动的。入口函数 main 随后调用用户定义的 main 函数。

<pre><code class="lang-go hljs"><span class="c1">// The main goroutine. </span><span class="c1"/><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="k">if</span> <span class="nx">iscgo</span> <span class="p">{</span> <span class="o">...</span> <span class="c1">// Start the template thread in case we enter Go from </span><span class="c1"/> <span class="c1">// a C-created thread and need to create a new thread. </span><span class="c1"/> <span class="nf">startTemplateThread</span><span class="p">()</span> <span class="nf">cgocall</span><span class="p">(</span><span class="nx">_cgo_notify_runtime_init_done</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">}</span> <span class="o">...</span> <span class="p">}</span> </code></code></pre>

如果我们进一步看看 startTemplateThread 函数的定义 <span class="invisible">https://</span><span class="visible">github.com/golang/go/bl</span><span class="invisible">ob/b1be1428dc7d988c2be9006b1cbdf3e513d299b6/src/runtime/proc.go#L1837-L1851</span><span class="ellipsis"/> ,会看到线程实际上是它调用 newm 启动的。

<pre><code class="lang-go hljs"><span class="kd">func</span> <span class="nf">startTemplateThread</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="nf">newm</span><span class="p">(</span><span class="nx">templateThread</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">...</span> <span class="p">}</span> </code></code></pre>

这个函数名字好奇怪。new 的意思大家都知道,这个 m 是什么呢?

在我们进一步解释 Cgo 不仅影响 goroutines 的启动,而且影响 goroutines 的调度之前,需要把 Go scheduling 机制中的几个术语解释一下。

<ul><li>M:是 machine 的缩写,指的是 thead。在 Go runtime 里对应 type m struct。源码在这里。</li><li>G:是 goroutine 的缩写。在 Go runtime 里对应 type g struct。源码在这里。</li><li>P: 是 processor 的缩写。在 Go runtime 里对应 type p struct。源码在这里。请注意,P 并不简单对应一个 CPU core,而是指执行 Go code 需要的资源,当然包括 core,也包括在这个 core 上排队等待被执行的 G 们,甚至包括用这个 core 执行这些 G 们的 M(如果 P 的状态不是空闲的话)。</li></ul>

如果一个 Go 程序通过 Cgo 调用了 C 程序,这段 C 程序的执行不需要 P —— 因为 P 是用来执行 Go 程序的资源。不过这段 C 程序和 Go 程序一样,是需要被一个 M 来执行的。换句话说,M 用 P 来执行 Go 程序,M 执行 C 程序的时候不用 P。这一点在后面的章节里我们通过例子来解释。

上面几个概念的列表来自 Go runtime 源码里的注释 <span class="invisible">https://</span><span class="visible">github.com/golang/go/bl</span><span class="invisible">ob/b1be1428dc7d988c2be9006b1cbdf3e513d299b6/src/runtime/proc.go#L19-L80</span><span class="ellipsis"/> 。 我对它们的理解有两个来源。一是阅读了两个很不错的系列文章:

<ul><li><span class="invisible">https://www.</span><span class="visible">ardanlabs.com/blog/2018</span><span class="invisible">/08/scheduling-in-go-part1.html</span><span class="ellipsis"/> </li><li><span class="invisible">https://</span><span class="visible">medium.com/a-journey-wi</span><span class="invisible">th-go/tagged/goroutines</span><span class="ellipsis"/> </li></ul>

二是阅读源码和做试验 —— 类似本文开头的 sample code。

所以,下面的系列文章和这两篇英语的系列文章内容上有交叠,大家不用奇怪。但是交叠不是重叠,结合源码的分析,是这个系列文章的特点。毕竟源码是我们开发 GoTorch 的基础。所以接下来,我们要从科普模式进入 hard core 模式了。

大家准备好看汇编代码了吗?如果准备好了,请点击下文

<span class="LinkCard-backdrop" style="background-image:url(https://zhstatic.zhihu.com/assets/zhihu/editor/zhihu-card-default.svg)"/><span class="LinkCard-content"><span class="LinkCard-text"><span class="LinkCard-title" data-text="true">王益:Go的隐秘世界:Go程序的启动和runtime初始化</span><span class="LinkCard-meta"><span style="display:inline-flex;align-items:center">​<svg class="Zi Zi--InsertLink" fill="currentColor" viewbox="0 0 24 24" width="17" height="17"><path d="M13.414 4.222a4.5 4.5 0 1 1 6.364 6.364l-3.005 3.005a.5.5 0 0 1-.707 0l-.707-.707a.5.5 0 0 1 0-.707l3.005-3.005a2.5 2.5 0 1 0-3.536-3.536l-3.005 3.005a.5.5 0 0 1-.707 0l-.707-.707a.5.5 0 0 1 0-.707l3.005-3.005zm-6.187 6.187a.5.5 0 0 1 .638-.058l.07.058.706.707a.5.5 0 0 1 .058.638l-.058.07-3.005 3.004a2.5 2.5 0 0 0 3.405 3.658l.13-.122 3.006-3.005a.5.5 0 0 1 .638-.058l.069.058.707.707a.5.5 0 0 1 .058.638l-.058.069-3.005 3.005a4.5 4.5 0 0 1-6.524-6.196l.16-.168 3.005-3.005zm8.132-3.182a.25.25 0 0 1 .353 0l1.061 1.06a.25.25 0 0 1 0 .354l-8.132 8.132a.25.25 0 0 1-.353 0l-1.061-1.06a.25.25 0 0 1 0-.354l8.132-8.132z"/></svg></span>zhuanlan.zhihu.com</span></span><span class="LinkCard-imageCell"></span></span>

到此这篇关于“ Go的隐秘世界:一个Goroutine要几个Thread”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Go的隐秘世界:有Thread为啥还要Goroutine
Go的隐秘世界:从 Cgo 到 Goroutine 调度
Go的隐秘世界:一个Goroutine要几个Thread
简单理解 Goroutine 是如何工作的
Go的隐秘世界:Go程序的启动和runtime初始化
Go的隐秘世界:Goroutine调度机制概览
也谈goroutine调度器
Go:Goroutine 的切换过程实际上涉及了什么
Goroutine的调度分析(一)
Goroutine并发调度模型深度解析&amp;手撸一个协程池

[关闭]
~ ~