99,36岁老码农现身说法

什么是主 goroutine?

它与我们自己启用的其他goroutine 有什么不同?

本文就来为你一一解答!

以下内容节选自**《Go语言极简一本通:零基础入门到项目实战》**一书!

并发

串行程序,即程序的执行顺序和程序的编写顺序一致,整个程序只有一个上下文,就是一个栈,一个堆。

并发程序,则需要运行多个上下文,对应多个调用栈。每个进程在运行时,都有自己的调用栈和堆,有一套完整的上下文。操作系统在调用时,会保证被调度进程的上下文环境,待该进程获得时间后,再将该进程的上下文恢复到系统中。

串行的代码是逐行执行的,是确定的,而并发引入了不确定性。线程通信只能采用共享内存的方式,为了保证共享内存的有效性,可以加锁,但是这样又引入了死锁的风险。

并发的优势如下:

(1)可以充分利用CPU 核心的优势,提高程序的执行效率。

(2)并发能充分利用CPU 与其他硬件设备的异步性,如文件操作等。

下面介绍3种并发模式。

1**.多进程是操作系统层面的并发模式**

所有的进程都由内核管理。进程描述的是程序的执行过程,是运行着的程序。

一个进程其实就是一个程序运行时的产物。

电脑为什么可以同时运行那么多应用程序?手机为什么可以有那么多App 同时在后台刷新?

这是因为在它们的操作系统之上有多个代表着不同应用程序的进程在同时运行。

操作系统会为每个独立的程序创建一个进程,进程可以装下整个程序需要的资源。例如,程序执行的进度、执行的结果等,都可以放在里面。在程序运行结束后,再把进程销毁,然后运行下一个程序,周而复始。

进程在程序运行中是非常占用资源的,无论是否会用到全部的资源,只要程序启动了,就会被加载到进程中。

优势是进程互不影响,劣势是开销非常大。

2**.多线程属于系统层面的并发模式,也是使用最多、最有效的一种模式**

线程是在进程之内的,可以把它理解为轻量级的进程。它可以被视为进程中代码的执行流程。这样在处理程序的运行和记录中间结果时,就可以使用更少的资源。待资源用完,线程就会被销毁。

线程要比进程轻量级很多。一个进程至少包含一个线程。如果一个进程只包含一个线程,那么它里面的所有代码都只会被串行地执行。

每个进程的第一个线程都会随着该进程的启动而被创建,它们被称为其所属进程的主线程。同理,如果一个进程中包含

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

多个线程,那么其中的代码就可以被并发地执行。

除进程的第一个线程外,其他的线程都是由进程中已存在的线程创建出来的。也就是说,主线程之外的其他线程都只能由代码显式地创建和销毁。这需要我们在编写程序时进行手动控制。

优势是比进程开销小一些,劣势是开销仍然较大。

3**.**Goroutine

从本质上说,goroutine 是一种用户态线程,不需要操作系统进行抢占式调度。

在Go 程序中,Go 语言的运行时系统会自动地创建和销毁系统级的线程。

系统级线程指的是操作系统提供的线程,而对应的用户级线程(goroutine)指的是架设在系统级线程之上的,由用户(或者说我们编写的程序)完全控制的代码执行流程。

用户级线程的创建、销毁、调度、状态变更,以及其中的代码和数据都完全需要我们的程序自己去实现和处理,其优势如下:

(1)因为它们的创建和销毁不需要通过操作系统去做,所以速度很快,可以提高任务并发性。编程简单、结构清晰。

(2)由于不用操作系统去调度它们的运行,所以很容易控制,并且很灵活。

协程并发模型

在Go 语言中,不要通过共享数据来通信,恰恰相反,要通过通信的方式来共享数据。

Go 语言不仅有goroutine,还有强大的用来调度 goroutine、对接系统级线程的调度器。

调度器是 Go 语言运行时系统的重要组成部分,它主要负责统筹调配 Go 并发编程模型中的三个主要元素,即G**(**goroutine **的缩写)、Pprocessor 的缩写)和 M(**machine 的缩写),如下图所示。

其中,M 指的就是系统级线程。而P 指的是一种可以引用若干个G,且能够使这些G 在恰当的时机与M 进行对接,并得到运行的中介。

从宏观上说,由于P 的存在,G 和M 可以呈现出多对多的关系。当一个正在与某个M 对接并运行着的G,需要因某个事件(比如等待 I/O 或锁的解除)而暂停运行时,调度器总会及时地发现,并把这个G 与那个M 分离开,以释放计算资源供那些等待运行的G 使用。

而当一个G 需要恢复运行时,调度器又会尽快地为它寻找空闲的计算资源(包括M)并安排运行。另外,当M 不够用时,调度器会向操作系统申请新的系统级线程,而当某个M 已无用时,调度器又会负责把它及时地销毁。

程序中的所有 goroutine 也都会被充分地调度,其中的代码也都会被并发地运行,即使goroutine 数以十万计,仍然可以如此。

什么是主 goroutine**?它与我们自己启用的其他****goroutine** 有什么不同?

先来看下面的代码:

package main

import “fmt”

func main() {

for i := 0; i < 10; i++ {

go func() {

fmt.Println(i)

}()

}

}

这段代码只在main 函数中写了一条for 语句。这条for 语句中的代码会迭代运行10 次,并有一个局部变量i 表示当次迭代的序号,该序号是从0 开始的。在这条for 语句中仅有一条Go语句,在这条Go 语句中也仅有一条语句,该语句调用了fmt.Println 函数,想要打印出变量i 的值。

这个程序很简单,只有三条语句。这个程序被执行后,会打印出什么内容呢?

答案是:大部分计算机执行后,屏幕上不会有任何内容被打印出来。


版权声明:本文为m0_64382743原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。