导语
我们都知道Golang的最大特性就是Goroutine(协程)的设计,那究竟什么是协程呢?它和计算机中的进程和线程又有哪些区别呢?为什么协程可以同时并发,几乎不占内存,想必在你的心中有了类似这样很多的疑问,那么现在我们一起来看看这些是为什么吧。
进程和线程的说明
在了解协程之前,我们需要回顾一下计算机操作系统的知识
- 进程就是程序在操作系统中的依次执行过程,是系统进行资源分配和调度的基本单位。
- 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
- 一个进程可以创建和销毁多个线程,同时一个进程中的多个线程可以并发执行
-
一个程序至少有一个进程,一个进程至少有一个线程
为了更好的理解,我们画一张图:
那么并发和并行又有啥区别呢?
- 多线程程序在单核上运行就是并发
- 多线程程序在多核上与运行就是并行
并发:在一个CPU上,比如说有10个线程,每个线程执行10毫秒(进行轮询)从我们的角度看,这10个线程似乎同时在循行,但是实际上某个时间点只有一个线程在运行,就是并发
并行:现在反过来,我们有10个CPU,有10个线程,这10个线程运行在不同的CPU上,互不干扰就是并行。
Go协程
了解了以上进程和线程、并发与并行,那么Go协程又是什么呢?在一个Go程序中,我们通常成main函数对应了我们的主线程,而在主线程,开辟的一个轻量级的线程则称之为协程。下面是Go协程的特点
- 独立的栈空间:所谓栈空间就是线程独有的,保存其运行状态和局部自动变量的
- 共享程序堆空间:大家共有的空间,用完需要及时的还给计算机。
- 调度由用户控制:用户可以任意控制线程间的切换。开启和结束
- 轻量级的线程:属于线程,不占用太多的CPU,但可以充分利用CPU,完成相应功能。
现在我们简单画一个主线程和协程的运行流程图来描述它们的关系
在上面的这个图中,可以可看到协程的运行是在主线程的控制下,因此若控制一个多个协程,就要学会阻塞的相关知识。因为Go的线程轻量级,属于逻辑态,所以可以轻松启动上万个协程,而向我们所熟知的C和Java的线程是属于内核态,往往几千个线程就会耗光CPU。
协程模型原理剖析
MPG模式介绍
一个G的执行需要P和M的支持,M与P关联后,就会形成一个有效的G运行环境:工作线程+上下文环境。
- M为操作系统的主线程,又叫工作线程,用来关联上下文环境P,如果没有足够的工作线程,就会创建新的M
- Go运行时会适时的让上下文环境P与不同的工作线程M建立连接或者断开,以使P中的可运行G即使获得运行时机。
-
一个G代表一个Go协程goroutine,即go函数。go语句会被传递为内部函数newproc的调用。
下面用图来做更详细的解释:
在上面的这张图中我们可以看到程序有3个M,如果三个M都在一个cpu上就是我们所说的并发,不在同一个就是并行,三个M正在执行相关的G,但是不同的是,在相关G的后面有着不同数量的G正在等待。接着我们再看一张图
仔细观察我们会发现原来M2的3个G跑到了M1下,其实在程序运行的过程中会发生阻塞,因此当M2出现阻塞,G就会找相应空闲的G,所以可以充分利用我们的线程,而不会造成资源浪费。如果这时M2不阻塞,就会归为空闲的G等待着被唤醒。
陷阱
你可能会发现并发的时候会出现相应的资源争夺问题,比如有一个数组,当协程1要去写入内容的时候,协程2要读内容,这样就会造成数组不知道要写还是读,数据会出现严重的不一致,因此在我们用协程去解决问题的时候,应该给我们的协程加上锁,这样就不会出现同一个资源出现争夺的情况,Go中同样也提供了管道,并且线程安全,来保障我们的资源不会出现争夺。
推荐阅读
- 开发环境搭建(持续更新中)
- RabbitMQ系列笔记介绍篇
- Golang中Modle包的使用
- goriila context深入学习笔记
- Go Context深入学习笔记
- 基于Nginx和Consul构建高可用及自动发现的Docker服务架构
- 关于log日志的深入学习笔记
本文欢迎转载,转载请联系作者,谢谢!
- 公众号【常更新】:无崖子天下无敌
- GitHub:https://github.com/yuwe1
- CSDN【看心情更新】: https://blog.csdn.net/weixin_40051278
- 博客地址【定期更新】:https://mowuya.cn/
有疑问加站长微信联系(非本文作者)