作为Windows用户,我知道操作系统线程消耗约1 Mb的内存,由于By default, Windows allocates 1 MB of memory for each thread's user-mode stack.
如果操作系统线程更贪吃, golang
如何为每个goroutine
使用golang
的内存。 goroutine
什么样的虚拟线程?
Goroutines不是线程,他们是(从规格 ):
在同一地址空间内的一个独立的并发控制线程或goroutine 。
Effective Go将它们定义为:
它们被称为goroutines,因为现有的术语(线程,协程,进程等等)传递的是不准确的内涵。 goroutine有一个简单的模型:它是一个在同一个地址空间中与其他goroutines同时执行的函数。 它是轻量级的,比堆栈空间的分配花费更多。 堆栈起点很小,所以它们很便宜,而且根据需要分配(并释放)堆存储空间以增加堆栈。
Goroutines没有自己的线程。 相反,多个goroutines被多路复用到相同的OS线程上,所以如果一个应该阻塞(例如等待I / O或者阻塞通道操作),其他的继续运行。
可以使用runtime.GOMAXPROCS()
函数来设置同时执行goroutine的实际线程数。 从runtime
包文档引用:
GOMAXPROCS变量限制了可以同时执行用户级Go代码的操作系统线程的数量。 系统调用中代表Go代码的线程数量没有限制; 那些不符合GOMAXPROCS限制。
请注意,在当前实现默认情况下,只有一个线程用于执行goroutines。
1 MiB是默认值 ,正如您正确指出的那样。 你可以很容易地选择自己的堆栈大小(但是,最小值仍然比〜8kB大得多)。
也就是说,goroutines不是线程。 他们只是具有共同任务的任务,类似于Python的。 goroutine本身就是做你想要的代码和数据。 还有一个单独的调度程序(在多个操作系统线程上运行),它们实际执行该代码。
在伪代码中:
loop forever take job from queue execute job end loop
当然, execute job
部分可以非常简单,也可以非常复杂。 你可以做的最简单的事情就是执行一个给定的委托(如果你的语言支持这样的话)。 实际上,这只是一个方法调用。 在更复杂的情况下,例如,还可以存在诸如恢复某种上下文,处理延续和协作任务收益的东西。
这是一个非常轻量级的方法,在进行异步编程时非常有用(现在几乎所有东西都是:))。 很多语言现在都支持类似的东西 – Python是我在第一个看到这个(“tasklets”)之前的东西。 当然,在没有先发制人的多线程环境下,这几乎是默认的。
在C#中,例如,有Task
。 它们并不完全相同,但在实践中,它们非常接近 – 主要区别在于Task
使用线程池中的线程(通常是),而不是单独的专用“调度程序”线程。 这意味着如果你启动了1000个任务,它们有可能被1000个独立的线程运行; 在实践中,它会要求你编写非常糟糕的Task
代码(例如,只使用阻塞I / O,睡眠线程,等待等待句柄等)。 如果你使用Task
来实现异步非阻塞I / O和CPU的工作,那么在实际操作中它们会非常接近goroutines。 理论有点不同:)
编辑:
为了澄清一些混淆,下面是典型的C#异步方法的样子:
async Task<string> GetData() { var html = await HttpClient.GetAsync("http://www.google.com"); var parsedStructure = Parse(html); var dbData = await DataLayer.GetSomeStuffAsync(parsedStructure.ElementId); return dbData.First().Description; }
从GetData
方法的角度来看,整个处理过程是同步的 – 就好像您根本没有使用异步方法一样。 关键的区别是,当你在做“等待”的时候,你并没有使用线程。 但忽略这一点,它几乎和编写同步分块代码一样。 这也适用于共享状态的任何问题,当然,多线程问题在await
和阻塞多线程I / O方面没有太大区别。 使用Task
可以避免更容易,但仅仅是因为你拥有的工具,而不是因为Task
的“魔法”。
goroutines在这方面的主要区别在于Go并没有真正的通常意义上的阻塞方法。 而不是阻塞,他们排队他们特定的异步请求,并屈服。 当操作系统(以及Go中的任何其他层 – 我对内部工作没有深入的了解)收到响应后,会将其发送到goroutine调度程序,goroutine调度程序又知道“等待”响应的goroutine是现在准备恢复执行; 当它实际上得到一个插槽时,它将继续从“阻塞”调用,如果它真的阻塞 – 但实际上,它是非常类似于C#正在await
。 没有什么根本的区别,C#的方法和Go的方法有很多不同,但并不是那么大 。
还要注意,这与旧版Windows系统使用的方法基本相同,没有先发制人的多任务处理 – 任何“阻塞”方法都只是让线程的执行回到调度器。 当然,在这些系统上,你只有一个CPU核心,所以你不能一次执行多个线程,但是原理还是一样的。
goroutines是我们所说的绿色线程 。 他们不是操作系统线程,去调度程序负责他们。 这就是为什么他们可以有更小的内存占用。