一、Go语言中Goroutine的基本原理

Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。

Go语言的goroutine是一个独立的工作单元,

Go 语言的并发同步模型来自一个叫作通信顺序进程(Communicating Sequential Processes,CSP)的范型(paradigm)。

CSP 是一种消息传递模型,通过在goroutine 之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。消息的传递通过Go语言中的Channel(通道)来实现。

补充:CSP模型和Actor模型的区别《七周七并发模型》

补充:复习什么是线程、什么是进程

进程(process) 看作一个包含了应用程序在运行中需要用到和维护的各种资源的容器。

线程(Thread)是一个执行空间,这个空间会被操作系统调度来运行函数中所写的代码。

每个进程至少包含一个线程,每个进程的初始线程被称作主线程。因为执行这个线程的空间是应用程序的本身的空间,所以当主线程终止时,应用程序也会终止。

下图就是进程和线程的简要关系。

补充:句柄是什么?https://blog.csdn.net/wenzhou1219/article/details/17659485

我的理解句柄就是给用户操作内核资源的指针,指向指针的指针。【把手】和【门】的关系,使用们把手能转动整个门(系统资源)

操作系统会在物理处理器上调度线程来运行,而Go 语言的运行时会在逻辑处理器上调度goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。下面我们就来简单描述一下goroutine执行的流程。

首先来了解一下操作系统线程、逻辑处理器和本地运行队列

全局运行队列:刚创建的goruntine会被安排到这里面,通过一些调度算法,分配给逻辑处理器

操作系统线程:这个就是传统意义上的线程,用于具体goroutine的执行,他会和一个逻辑器进行绑定

逻辑处理器:里面维护着一个本地的队列,用于本地队列里面goroutine的调度

本地运行队列:装载着待执行的goruntine

支撑整个调度器的主要有4个重要结构,分别是M、G、P、Sched,前三个定义在runtime.h中,Sched定义在proc.c中。

  • Sched结构就是调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。
  • M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
  • P全称是Processor,处理器,它的主要用途就是用来执行goroutine的,所以它也维护了一个goroutine队列,里面存储了所有需要它来执行的goroutine,这个P的角色可能有一点让人迷惑,一开始容易和M冲突,后面重点聊一下它们的关系。
  • G就是goroutine实现的核心结构了,G维护了goroutine需要的栈、程序计数器以及它所在的M等信息。

整个过程描述:

当创建一个Goroutine的时候,先放到全局队列当中,然后会把这个Goroutine分配到一个逻辑处理器的本地队列中,这个逻辑处理器会绑定一个操作系统线程,由这个线程去执行Goroutine的代码。

https://www.zhihu.com/question/20862617

生动描述:

地鼠(gopher)用小车运着一堆待加工的砖。M就可以看作图中的地鼠,P就是小车,G就是小车里装的砖。一图胜千言啊,弄清楚了它们三者的关系,下面我们就开始重点聊地鼠是如何在搬运砖块的。

1.runqget, 地鼠(M)试图从自己的小车(P)取出一块砖(G),当然结果可能失败,也就是这个地鼠的小车已经空了,没有砖了。

2.findrunnable, 如果地鼠自己的小车中没有砖,那也不能闲着不干活是吧,所以地鼠就会试图跑去工场仓库取一块砖来处理;工场仓库也可能没砖啊,出现这种情况的时候,这个地鼠也没有偷懒停下干活,而是悄悄跑出去,随机盯上一个小伙伴(地鼠),然后从它的车里试图偷一半砖到自己车里。如果多次尝试偷砖都失败了,那说明实在没有砖可搬了,这个时候地鼠就会把小车还回停车场,然后睡觉休息了。如果地鼠睡觉了,下面的过程当然都停止了,地鼠睡觉也就是线程sleep了。

3.wakep, 到这个过程的时候,可怜的地鼠发现自己小车里有好多砖啊,自己根本处理不过来;再回头一看停车场居然有闲置的小车,立马跑到宿舍一看,你妹,居然还有小伙伴在睡觉,直接给屁股一脚,“你妹,居然还在睡觉,老子都快累死了,赶紧起来干活,分担点工作。”,小伙伴醒了,拿上自己的小车,乖乖干活去了。有时候,可怜的地鼠跑到宿舍却发现没有在睡觉的小伙伴,于是会很失望,最后只好向工场老板说——”停车场还有闲置的车啊,我快干不动了,赶紧从别的工场借个地鼠来帮忙吧。”,最后工场老板就搞来一个新的地鼠干活了。

4.execute,地鼠拿着砖放入火种欢快的烧练起来。

注: “地鼠偷砖”叫work stealing,一种调度算法。

到这里,貌似整个工场都正常的运转起来了,无懈可击的样子。不对,还有一个疑点没解决啊,假设地鼠的车里有很多砖,它把一块砖放入火炉中后,何时把它取出来,放入第二块砖呢?难道要一直把第一块砖烧练好,才取出来吗?那估计后面的砖真的是等得花儿都要谢了。这里就是要真正解决goroutine的调度,上下文切换问题。

goroutine的阻塞

当一个OS线程M0陷入阻塞时(如下图),P转而在运行M1,图中的M1可能是正被创建,或者从线程缓存中取出。

简单解释就是,当当前的Goroutine在阻塞,就把他和当前线程绑定,让当前的线程继续执行这个Goroutine(就是等待),其他的goroutine随着逻辑处理器被绑定到另一个线程上,继续执行。

当阻塞的线程返回时,它必须尝试取得一个逻辑处理器来运行goroutine,一般情况下,它会从其他的OS线程那里拿一个P过来,如果没有拿到的话,它就把goroutine放在一个全局运行队列里,然后自己睡眠(放入线程缓存里)。所有的P也会周期性的检查global runqueue并运行其中的goroutine,否则global runqueue上的goroutine永远无法执行。

还有就像下面的图片,左边的线程一个满载状态,一个没有goroutine,此时的做法就是会进行重新分配,就想右侧图展示

参考 :http://morsmachine.dk/go-scheduler

二、Go语言中的并发和并行

并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。

并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。

并发是一次处理很多事情,并行是同时做很多事情。

应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。

应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。

应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。

应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。

简单讲:并行就是同时做很多事 并发就是一堆事情一个时间点来了,然后通过分片等方式感觉像都在执行

Go语言的并发的更多相关文章

  1. [日常] GO语言圣经-并发获取多个URL

    go语言圣经-并发获取多个URL 1.GO最新奇的特性就是对并发编程的支持,goroutine和channel 2.goroutine是一种函数的并发执行方式,而channel是用来在goroutin ...

  2. 【Golang详解】go语言中并发安全和锁

    go语言中并发安全和锁 首先可以先看看这篇文章,对锁有些了解 [锁]详解区分 互斥锁.⾃旋锁.读写锁.乐观锁.悲观锁 Mutex-互斥锁 Mutex 的实现主要借助了 CAS 指令 + 自旋 + 信号 ...

  3. go语言之并发

    简介           多核处理器越来越普及,那有没有一种简单的办法,能够让我们写的软件释放多核的威力?答案是:Yes.随着Golang, Erlang, Scale等为并发设计的程序语言的兴起,新 ...

  4. Go语言 7 并发编程

    文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 今天我们学习Go语言编程的第七章,并发编程.语言级别 ...

  5. go语言之并发编程一

    Go语言最大的优势就在于并发编程.Go语言的关键字go就是开启并发编程也就是goroutine的唯一途径.一条go语句以为着一个函数或方法的并发执行.Go语句是由go关键字和表达式组成.比如下面的这种 ...

  6. Go语言之并发编程(三)

    Telnet回音服务器 Telnet协议是TCP/IP协议族中的一种.它允许用户(Telnet客户端)通过一个协商过程与一个远程设备进行通信.本例将使用一部分Telnet协议与服务器进行通信. 服务器 ...

  7. Go语言之并发编程(二)

    通道(channel) 单纯地将函数并发执行是没有意义的.函数与函数间需要交换数据才能体现并发执行函数的意义.虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题 ...

  8. Go语言之并发编程(一)

    轻量级线程(goroutine) 在编写socket网络程序时,需要提前准备一个线程池为每一个socket的收发包分配一个线程.开发人员需要在线程数量和CPU数量间建立一个对应关系,以保证每个任务能及 ...

  9. Go语言_并发

    并发 Go 将并发结构作为核心语言的一部分提供.本节课程通过一些示例介绍并展示了它们的用法. Go 作者组编写,Go-zh 小组翻译. https://tour.go-zh.org/concurren ...

随机推荐

  1. iOS----------弹窗动画

    - (void)animationAlert:(UIView *)view { CAKeyframeAnimation *popAnimation = [CAKeyframeAnimation ani ...

  2. Android 网络框架 Retrofit2

    概述 Retrofit是一个OkHttp网络请求框架的封装库,Retrofit通过注解配置网络参数,可以按照我们的规则去构造实际的HTTP请求,能够灵活设置URL.头部.请求体.返回值等,是目前最优雅 ...

  3. eclipse如何修改android工程的包名?

    在我们android项目开发到一定的程度时由于需要,我们必须修改一下工程的包名,以便更好的发布我们的项目.但是在这个过程中有时候修改好了之后会出现一些错误.下面由小编一步步教你如何更改包名,和解决出现 ...

  4. java基础知识总结一:

      四种内部类 直接抛出异常 单例模式: 懒汉式单例.饿汉式单例.登记式单例     []关于内部类:  []关于异常: 直接捕捉并抛出异常:不需要给异常添加名字: if(i>10)throw ...

  5. MUI 实用教程

    MUI 实用JS教程: https://www.kancloud.cn/benhailong/mui/319751  MUI 实用教程: https://www.kancloud.cn/benhail ...

  6. Python运算符之翩若惊鸿,婉若游龙

    python中的运算符算术运算符:主要用于两个对象算数计算(加减乘除等运算)比较运算符:用于两个对象比较(判断是否相等.大于等运算)赋值运算符:用于对象的赋值,将运算符右边的值(或计算结果)赋给运算符 ...

  7. RHEL/Centos7 安装图形化桌面(转)

    RHEL/Centos7 安装图形化桌面   Linux是一个多任务的多用户的操作系统,好多linux爱好者在安装完linux后经常遇到一个问题——没有图形化桌面.今天小编在安装RHEL7的时候,一步 ...

  8. Windows10系统无法更新

    方法一: 1.先检查一下windows update服务是否开启,并禁用杀毒软件: 2.如果此服务已经启动,先尝试更换一下网络环境重新更新: 3.如果更换网络环境后依然无法更新,就删除windows ...

  9. JavaScript对象类型之创建对象

    引言 JavaScript中,可以通过对象直接量,关键字new(ECMAScript 5中的)Object.create(),函数来创建对象. 对象直接量 JavaScript中使用对象直接量来创建对 ...

  10. 我超级推荐的Navicat Premium 12的下载,破解方法

    今天给大家推荐一款炒鸡好用的数据库管理工具,使用它,可以很方便的连接各种主流数据库软件----Navicat Premium 12 但是,它是要钱的,不过我们可以使用破解机来破解它,步骤稍有些复杂,简 ...