教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang中Goroutine与线程

Golang中Goroutine与线程

发布时间:2021-12-02   编辑:jiaochengji.com
教程集为您提供Golang中Goroutine与线程等资源,欢迎您收藏本站,我们将为您提供最新的Golang中Goroutine与线程资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg>

我们在使用Go语言进行开发时,一般会使用goroutine来处理并发任务。那么大家有没有考虑过goroutine的实现机制是什么样的?很多同学会把goroutine与线程等同起来,但是实际上并不是这样的。在这边文章中,我们将介绍以下内容:

<ul><li>什么是goroutine?</li><li>Goroutine与线程的区别</li><li>Goroutine是如何调度的</li></ul><h3>1. 什么是goroutine?</h3>

Goroutine是建立在线程之上的轻量级的抽象。它允许我们以非常低的代价在同一个地址空间中并行地执行多个函数或者方法。相比于线程,它的创建和销毁的代价要小很多,并且它的调度是独立于线程的。在golang中创建一个goroutine非常简单,使用“go”关键字即可:

<pre><code class="lang-go hljs"><span class="token keyword">package</span> main <span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"time"</span> <span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function">learning</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"My first goroutine"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">go</span> <span class="token function">learning</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">/* we are using time sleep so that the main program does not terminate before the execution of goroutine.*/</span> time<span class="token punctuation">.</span><span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">*</span> time<span class="token punctuation">.</span>Second<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"main function"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

这段代码的输出是这样的:

<pre><code class="lang-go hljs">My first goroutinemain function </code></pre>

如果把Sleep去掉的话,输出就会变成:

<pre><code class="lang-go hljs">main function </code></pre>

这是因为,和线程一样,golang的主函数(其实也跑在一个goroutine中)并不会等待其它goroutine结束。如果主goroutine结束了,所有其它goroutine都将结束。

<h3>
2. Goroutine与线程的区别</h3>

许多人认为goroutine比线程运行得更快,这是一个误解。Goroutine并不会更快,它只是增加了更多的并发性。当一个goroutine被阻塞(比如等待IO),golang的scheduler会调度其它可以执行的goroutine运行。与线程相比,它有以下几个优点:

<ul><li>内存消耗更少
Goroutine所需要的内存通常只有2kb,而线程则需要1Mb(500倍)。</li><li>创建与销毁的开销更小
由于线程创建时需要向操作系统申请资源,并且在销毁时将资源归还,因此它的创建和销毁的开销比较大。相比之下,goroutine的创建和销毁是由go语言在运行时自己管理的,因此开销更低。
</li><li>切换开销更小
这是goroutine于线程的主要区别,也是golang能够实现高并发的主要原因。线程的调度方式是抢占式的,如果一个线程的执行时间超过了分配给它的时间片,就会被其它可执行的线程抢占。在线程切换的过程中需要保存/恢复所有的寄存器信息,比如16个通用寄存器,PC(Program Counter),SP(Stack Pointer),段寄存器等等。
而goroutine的调度是协同式的,它不会直接地与操作系统内核打交道。当goroutine进行切换的时候,之后很少量的寄存器需要保存和恢复(PC和SP)。因此gouroutine的切换效率更高。</li></ul><h3>
3.Goroutine的调度</h3>

真如前面提到的,goroutine的调度方式是协同式的。在协同式调度中,没有时间片的概念。为了并行执行goroutine,调度器会在以下几个时间点对其进行切换:

<ul><li>Channel接受或者发送会造成阻塞的消息</li><li>当一个新的goroutine被创建时</li><li>可以造成阻塞的系统调用,如文件、网络操作及Sleep等</li><li>垃圾回收</li></ul>

下面让我们来看一下调度器具体是如何工作的。Golang调度器中有三个概念

<ul><li>Processor(P)</li><li>OSThread(M)</li><li>Goroutines(G)</li></ul>

在一个Go程序中,可用的线程数是通过GOMAXPROCS来设置的,默认值是可用的CPU核数。我们可以用runtime包动态改变这个值。OSThread调度在processor上,goroutines调度在OSThreads上,如下图所示


Golang的调度器可以利用多processor资源,在任意时刻,M个goroutine需要被调度到N个OS threads上,同时这些threads运行在至多GOMAXPROCS个processor上(N <= GOMAXPROCS)。Go scheduler将可运行的goroutines分配到多个运行在一个或多个processor上的OS threads上。

每个processor有一个本地goroutine队列。同时有一个全局的goroutine队列。每个OSThread都会被分配给一个processor。最多只能有GOMAXPROCS个processor,每个processor同时只能执行一个OSThread。Scheculer可以根据需要创建OSThread。

在每一轮调度中,scheduler找到一个可以运行的goroutine并执行直到其被阻塞。

<blockquote>

Search in the local queueif not found Try to steal from other Ps’ local queue //see Fig 3 if not found Search in the global queue Also periodically it searches in the global queue (every ~ 1/70)

</blockquote>

由此可见,操作系统的一个线程下可以并发执行上千个goroutine,每个goroutine所占用的资源和切换开销都很小,因此,goroutine是golang适合高并发场景的重要原因。

到此这篇关于“Golang中Goroutine与线程”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Goroutine的调度分析(一)
goroutine 调度器
Golang中Goroutine与线程
golang的调度总结
[Go 教程系列笔记] goroutine(协程)
简单理解 Goroutine 是如何工作的
golang 深入浅出之 goroutine 理解
go 协程
golang中goroutine的调度
Go:Goroutine 的切换过程实际上涉及了什么

[关闭]
~ ~