一、概念介绍

Go 语言是原生支持语言级并发的,这个并发的最小逻辑单元就是 goroutine。goroutine 就是 Go 语言提供的一种用户态线程,当然这种用户态线程是跑在内核级线程之上的。当我们创建了很多的 goroutine,并且它们都是跑在同一个内核线程之上的时候,就需要一个调度器来维护这些 goroutine,确保所有的 goroutine 都使用 cpu,并且是尽可能公平的使用 cpu 资源。这与其他语言大不一样,不像以前我们要用 Thread 库来新建线程,还要用线程安全的队列库来共享数据。

一般的程序,如果没有特别的要求的话,是顺序执行的,这样的程序也容易编写维护。但是随着科技的发展、业务的演进,我们不得不变写可以并行的程序,因为这样有很多好处。比如你在看文章的时候,还可以听着音乐,这就是系统的并行,同时可以做多件事情,充分的利用计算机的多核,提升的软件运行的性能。

在操作系统中,有两个重要的概念:一个是进程、一个是线程。当我们运行一个程序的时候,比如你的 IDE 或者 QQ 等,操作系统会为这个程序创建一个进程,这个进程包含了运行这个程序所需的各种资源,可以说它是一个容器,是属于这个程序的工作空间,比如它里面有内存空间、文件句柄、设备和线程等等。

线程是一个执行的空间,比如要下载一个文件,访问一次网络等。线程会被操作系统调用,来在不同的处理器上运行编写的代码任务,这个处理器不一定是该程序进程所在的处理。操作系统过的调度是操作系统负责的,不同的操作系统可能会不一样,但是对于我们程序编写者来说,不用关心,因为对我们都是透明的。

一个进程在启动的时候,会创建一个主线程,这个主线程结束的时候,程序进程也就终止了,所以一个进程至少有一个线程,这也是我们在 main 函数里,使用 goroutine 的时候,要让主线程等待的原因,因为主线程结束了,程序就终止了,那么就有可能会看不到 goroutine 的输出。

Go 语言中并发指的是让某个函数独立于其他函数运行的能力,一个 goroutine 就是一个独立的工作单元,Go 的 runtime(运行时)会在逻辑处理器上调度这些 goroutine 来运行,一个逻辑处理器绑定一个操作系统线程,所以说 goroutine 不是线程,它是一个协程,也是这个原因,它是由 Go 语言运行时本身的算法实现的。

总结下几个概念:

概念 说明
进程 一个程序对应一个独立程序空间
线程 一个执行空间,一个进程可以有多个线程
逻辑处理器 执行创建的 goroutine,绑定一个线程
调度器 Go 运行时中的,分配 goroutine 给不同的逻辑处理器
全局运行队列 所有刚创建的 goroutine 都会放到这里
本地运行队列 逻辑处理器的 goroutine 队列

这一套管理、调度、执行 goroutine 的方式称之为 Go 的并发。并发可以同时做很多事情,比如有个 goroutine 执行了一半,就被暂停执行其他 goroutine 去了,这是 Go 控制管理的。所以并发的概念和并行不一样,并行指的是在不同的物理处理器上同时执行不同的代码片段,并行可以同时做很多事情,而并发是同时管理很多事情,因为操作系统和硬件的总资源比较少,所以并发的效果要比并行好的多,使用较少的资源做更多的事情,也是 Go 语言提倡的。

以上所述是 Go 的并发原理,那么Go的并行是怎样的呢?其实答案非常简单,多创建一个逻辑处理器就好了,这样调度器就可以同时分配全局运行队列中的 goroutine 到不同的逻辑处理器上并行执行。

二、代码分析

看一下代码:

func main() {var wg  sync.WaitGroupwg.Add(2)go func() {defer wg.Done()for i:=1; i<100;i++  {fmt.Println("A:",i)}}()go func() {defer wg.Done()for i:=1; i<100;i++  {fmt.Println("B:",i)}}()wg.Wait()
}

这是一个简单的并发程序。创建一个 goroutine 是通过 go 关键字的,其后跟一个函数或者方法即可。

这里的 sync.WaitGroup 其实是一个计数的信号量,使用它的目的是要 main 函数等待两个 goroutine 执行完成后再结束,不然这两个 goroutine 还在运行的时候,程序就结束了,看不到想要的结果。

sync.WaitGroup 的使用也非常简单,先是使用 Add 方法设设置计算器为 2,每一个 goroutine 的函数执行完之后,就调用 Done 方法减 1。Wait 方法的意思是如果计数器大于 0,就会阻塞,所以 main 函数会一直等待 2 个 goroutine 完成后,再结束。

运行这个程序,会发现 A 和 B 前缀会交叉出现,并且每次运行的结果可能不一样,这就是 Go 调度器调度的结果。

默认情况下,Go 默认是给每个可用的物理处理器都分配一个逻辑处理器,因为我的电脑是 4 核的,所以上面的例子默认创建了 4 个逻辑处理器,因此这个例子中同时也有并行的调度,如果我们强制只使用一个逻辑处理器,我们再看看结果。

func main() {runtime.GOMAXPROCS(1)var wg  sync.WaitGroupwg.Add(2)go func() {defer wg.Done()for i:=1; i<100;i++  {fmt.Println("A:",i)}}()go func() {defer wg.Done()for i:=1; i<100;i++  {fmt.Println("B:",i)}}()wg.Wait()
}

设置逻辑处理器个数也非常简单,在程序开头使用 runtime.GOMAXPROCS(1) 即可,这里设置的数量是 1。这样就可以避免 A 和 B 交叉打印。

这里我们不要误认为是顺序执行,这里之所以顺序输出的原因,是因为我们的 goroutine 执行时间太短暂了,还没来得及切换到第 2 个 goroutine,第 1 个 goroutine 就完成了。这里我们可以把每个 goroutine 的执行时间拉长一些,就可以看到并发的效果了。

对于逻辑处理器的个数,不是越多越好,要根据电脑的实际物理核数,如果不是多核的,设置再多的逻辑处理器个数也没用,如果需要设置的话,一般我们采用如下代码设置。

runtime.GOMAXPROCS(runtime.NumCPU())

所以对于并发来说,就是 Go 语言本身自己实现的调度,对于并行来说,是和运行的电脑的物理处理器的核数有关的,多核就可以并行并发,单核只能并发了。

三、参考链接

https://www.flysnow.org/2017/04/11/go-in-action-go-goroutine.html
https://www.zhihu.com/question/20862617/answer/131341519

Go语言 goroutine相关推荐

  1. TODO:Go语言goroutine和channel使用

    2019独角兽企业重金招聘Python工程师标准>>> TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(r ...

  2. channelinactive触发后不关闭channel_Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作...

    今天是golang专题第15篇文章,我们来继续聊聊channel的使用. 在我们的上篇文章当中我们简单介绍了golang当中channel的使用方法,channel是golang当中一个非常重要的设计 ...

  3. java case用法_Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作

    今天是golang专题第15篇文章,我们来继续聊聊channel的使用. 在我们的上篇文章当中我们简单介绍了golang当中channel的使用方法,channel是golang当中一个非常重要的设计 ...

  4. [Go语言入门] 14 Go语言goroutine和通道详解

    文章目录 14 Go语言goroutine和通道详解 14.1 goroutine 14.2 通道(channel) 声明通道变量 创建通道 通道操作 14.3 管道 14.4 单向通道 14.5 通 ...

  5. c++ 初始化 代码 应放在那里_Go语言goroutine调度器初始化 (12)

    先吐槽一下,知乎编辑器居然不支持汇编语言,代码的空格也给我弄没了,你说你把运算符两边的空格搞掉就搞掉吧,还能看,你为啥要把if, for后面的空格也搞掉啊... 本文是<Go语言调度器源代码情景 ...

  6. Go语言goroutine+channel+select简介

    goroutine: Go语言是原生支持语言级并发的,这个并发的最小逻辑单元就是goroutine.goroutine就是Go语言提供的一种用户态线程,这种用户态线程是跑在内核级线程之上的,gorou ...

  7. go语言goroutine的取消

    go语言中使用 contex实现goroutine的取消 package mainimport ("context""fmt""sync"& ...

  8. go语言--goroutine

    一.goroutine goroutine就是Go语言提供的一种用户态线程.Go自己实现了goroutine的调度器(Scheduler),Go的调度器由三部分组成: M:指的是Machine,一个M ...

  9. GO语言goroutine

    前言 go语言对于并发编程有原生的支持. 如下代码:创建10个协程并发执行. package mainimport ("fmt""time" )func mai ...

最新文章

  1. 离线安装Visual Studio Code插件
  2. 【Ubuntu】VirtualBox显卡驱动VBoxVGA、VBoxSVGA、VMSVGA +3D对播放视频的影响
  3. OD使用教程6 - 调试篇06|解密系列
  4. 10个免费在线逻辑关系图表图形设计制作工具
  5. GetLastError 函数返回值大全
  6. 在DevExpress中使用CameraControl控件进行摄像头图像采集
  7. tomcat常用的优化和配置
  8. CentOs6.5下安装vim7.4
  9. python-docx 如何获取当前字号_调整字号保护视力?专家有一个更好的建议
  10. lin通讯从节点同步间隔场_汽车行业必须知识--CAN FD通讯
  11. android源码包下载
  12. centos6 python3 django-uwsgi-nginx使用supervisor作为uWSGI的守护进程
  13. winserver 服务开机启动
  14. 小甲鱼【C语言】《带你学C带你飞》笔记
  15. node.js 使用数据校验 joi 报错:Cannot mix different versions of joi schemas
  16. zblog html代码,简单快速修改zblog模板的重要代码
  17. linux服务器下如何显示中文的图片,Linux服务器中文显示问题
  18. 乐行学院Redis5学习教程 第一章redis5的安装
  19. 【开发教程3】AI语音人脸识别(会议记录仪/人脸打卡机)-CC3200简介
  20. DecoupleSegNets学习总结

热门文章

  1. 中国芯片研究再获国际顶会最佳论文提名!清华魏少军、刘雷波团队出品
  2. 首个镜子分割网络问世,大连理工、鹏城实验室、香港城大出品 | ICCV 2019
  3. 十一.python面向对象(接口)abstractmethod,ABCMeta
  4. python的__new__方法和__del__方法
  5. Google Map API V3调用arcgis发布的瓦片地图服务
  6. 客户资产管理(Custom Asset Management)
  7. 数据中心网络架构 — 云数据中心网络 — 二层架构设计示例
  8. 5G NGC — PCF 策略控制功能
  9. 5G NGC — 会话管理模型 — 基于 Flow 的 QoS 模型
  10. Git 分布式版本控制系统