Go学习笔记 -- 并发原理
admin
2024-04-01 14:34:12
0

文章目录

    • Go 协程和普通线程对比
      • 内核级线程(线程)
        • 线程优点
        • 线程缺点
      • 用户级线程(协程)
        • 协程优点
        • 协程缺点
    • 调度器(GPM)
      • G\P\M 之间的工作关系
    • Go 使用协程
      • 创建协程
      • 注意

Go 协程和普通线程对比

       Go 拥有极强的并发编程能力,而 Go 并发编程强势原因,一部分原因是因为语法简单 ,还有一个更核心的原因是 Go 中协程 goroutine (用户态线程)的存在。
       在 Gogoroutine 是比普通线程(内核态线程)更加轻量化的存在。

内核级线程(线程)

       内核态线程简称线程,是在内核中维护了线程表进行跟踪监控的线程。是 CPU 调度和分派的基本单位,是具备进程某些属性,能够独立运行的更小单位,所以也称为轻量级进程。

       如果使用内核态线程在一个 CPU 上实现任务的并发,那么 CPU 会通过分配时间片的方式去执行任务,当正在运行的任务所分配的时间片用完了,就会切换到另一个任务,而在切换的之前会保存当前任务的状态(CPU寄存器、程序计数器中的内容),当下次再次切换到这个任务之前就会先加载之前保存到任务状态,这个过程称作上下文切换。

       而在上下文切换的过程该线程会从用户态转为内核态,加载时再由内核态转为用户态。而用户态和内核态切换的代价是很高的,从用户态到内核态的切换比较耗费资源的,而且内核资源是很珍贵的,所以内核态线程过多时,可能会出现内核资源耗尽的情况。

线程优点

  1. 多核 CPU 利用 :内核具有0级权限,因此可以在多个 CPU 上执行内核线程
  2. 操作系统级优化:线程之间是相对独立的,一个线程阻塞不会影响其他线程的执行。内核态线程在进行你 IO 操作时不需要进行系统调用 。

线程缺点

  1. 创建的时候需要切换到内核态,所以创建成本比较高。
  2. 切换的时候,需要用户态和内核态之间频繁的切换,切换成本较高。
  3. 线程数量有限制,当内核态线程过多时,会出现内核资源耗尽的情况。

用户级线程(协程)

       用户态线程简称协程,是完全由用户控制的线程,内核并不会感知到它的存在,用户级线程的创建、销毁、调度、状态变更以及其中的代码和数据都完全需要我们的程序自己去实现和处理。因为是存在在用户空间上的线程,所以在切换时不存在用户态和内核态的转换。

协程优点

  1. 不由内核管理,由用户自主管理,创建成本比较低。
  2. 不存在内核态和用户态的切换,切换成本比较低。
  3. 不需要操作系统去调度,所以可以跨操作系统,并且更加灵活、更容易控制。

协程缺点

  1. 因为操作系统的内核看不到协程,所以同属于一个进程的协程只能占有一个核,不能发挥多核优势。
  2. 操作系统不能主动调度协程,所以后面的协程只能等待前面的写成执行完才能获取到 CPU 资源。
  3. 同上,当前协程阻塞会导致后面的写成无法执行。
  4. 内核协作成本高,当要进行一些高权限的操作,比如读写文件时,需要频繁地进行用户态和内核态的切换。

调度器(GPM)

       GPMGo 语言运行时系统的重要组成部分
       其中 G 代表 goroutine 即协程,M 代表 machine 即系统线程、P 代表 Proccessor 代表写成和线程之间的联系

简单点理解就是:

G 是要被执行的任务实例
M 是实际的执行载体,需要绑定 P 成为一个执行单元才能调度 G
P 可以看作是任务处理器,P 中保存 M 执行 G 时的一些资源,P 决定了哪个 G 能配分配到哪个 M 上

G\P\M 之间的工作关系

       G 要调度到 M 上才能运行,M 需要关联 P 才可以执行 Go 代码,但当处理阻塞或系统调用中时,M 可以不用关联 P

       当进程启动时,会创建若干个(一般是内核数量)系统线程 M 和任务处理器 P,并一一绑定,因为一个线程对应一个 CPU 就不会出现上下文的切切换,能更大限度的节省资源。同时每一个任务处理器 P 都会对应一个私有的任务队列,但是私有队列有上限的,所以所有任务处理器还会有一个公用的全局队列。

       当有一个 G 被创建时,就会被放到一个任务处理器 P 的私有队列中等待被调度。如果所有的私有队列都满了,就会被放到全局队列中去。当 P 寻找 G 的时候,会先从自己的私有队列中寻找,如果没找到,再去全局队列中寻找,如果还是没有,它会去抢别的 P 中的 G。当任何地方都不能找到需要调度的 G 时,MP 则会断开连接。

       当 G 中进行了系统调用,则 M 也会进入系统调用状态,此时 P 会去寻找其他未在工作的 M 并为其寻找需要调度的任务。当 G 完成了系统调用,会进入空闲的私有队列或者全局队列,然后等待再次被调度。

       所以,GM 之间没有直接的联系,一个 G 可能被不同的 M 调度,一个 M 也可以调度不同的 G

Go 使用协程

创建协程

通过关键字 go 创建一个协程
一:普通函数创建

func goroutineTest(i int) {fmt.Println(i)
}
func main() {for i := 0; i < 10; i++ {go goroutineTest(i)}time.Sleep(time.Millisecond * 500)
}

二:匿名函数创建

func main() {for i := 0; i < 10; i++ {go func() {fmt.Println(i)}()}time.Sleep(time.Millisecond * 500)
}

三:匿名带参数函数创建

func main() {for i := 0; i < 10; i++ {go func(str string) {fmt.Println(str)}(fmt.Sprintf("%s%d", "s", i))}time.Sleep(time.Millisecond * 500)
}

注意

一:主协程不会等待子协程执行完毕
       以下列代码为例,一般来说控制台不会打印任何东西。原因是当代码运行到 go goroutineTest(i) 这一行时,并不是真正的会运行它,而是将它放到了任务队列中。所以可能子协程还没开始执行,主协程就已经退出了。所以我们可以再主协程加上等待。

func goroutineTest(i int) {fmt.Println(i)
}
func main() {for i := 0; i < 10; i++ {go goroutineTest(i)}time.Sleep(time.Millisecond * 500)
}

二:协程的执行没有顺序
       同上,当写成创建是,并不是立即执行,而是被分到不同的队列中等待被调度,而调度是不能保证先后顺序的(只能保证同一个队列中的任务先进先出),因此上述代码打印出来的结果大部分情况下时乱序的。
三:无参匿名函数中的变量变化

func main() {for i := 0; i < 3; i++ {go func() {fmt.Println(i)}()}time.Sleep(time.Millisecond * 500)
}

这段代码的打印结果如下:

2
2
3

       因为匿名函数不携带参数信息,只有匿名方法执行到 fmt.Println(i) 这一行时才会去读取 i 的值。又因为协程执行是有延迟的,所以当执行到这一句时, i 的值可能已经发生了变化。

下一篇再研究主协程等待子协程执行完毕以及确保子协程执行顺序的方法…

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...