




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
并发概念(计算机)
一、串行程序、并发程序、并行程序
串行程序:指只能被顺序执行的指令列表。
并发程序:并发程序属于程序,其内部是由多个部分(串行程序)组
合在一起,这些部分组合在一起构成了一个整体,叫做并发程序。也
叫单元
并行程序:指可以在并行的硬件上(CPU,服务器),运行的并发程
序。
二、并发系统、并行系统
并发系统:程序和程序之间是通过协议协商一致后形成通信。并发系
统就是多个并发程序间构成的,看作是一个系统。
并行系统:指并发系统以并行的方式存在。每个并发系统中的程
序,很有可能会部署在多个并行硬件上(服务器)同时运行,也叫做
分布式系统。
三、并发程序内部交互
1.什么是并发程序的内部交互?
并发程序中存在多个串行程序,这些串行程序之间可能会存在数据交
互的需求。比如多个串行程序对一个共享资源进行访问(数据库,消
息队列);又或者在它们之间传递一些数据。
在这些需求的环境下,协调它们的执行,就要涉及到同步。
2.同步的作用
避免在并发访问共享资源时发生的冲突,可以有条不紊的传递数据。
3.同步的原则
当程序要访问一个共享资源时,就必须请求该共享资源并获取对应的
访问权;反之如果程序不再需要一个共享资源,就要放弃该资源的访
问权。(释放资源,Close())
而应该等到其释放资源后在进行访问(锁)。结论:一个共享资源在
同一时刻内,只能被一个程序占用。
同步会导致系统资源的浪费,耦合性太强。所以有了异步通信的思想。
4.异步通信
发送方可以不加延迟的将消息发送出去,接收方也不会造成发送方的
等待。(解耦)
数据会被临时存储在一个通道缓存(IPC)的通道中。通道缓存是一
种特殊的共享资源,可以被多个程序使用。接收方准备就绪后无需知
道发送方,直接从通道缓存中获取数据。
多线程编程概念(计算机)
一、什么是多线程编程
POSIX:可移植性的Unix操作系统接口。
NPTL:Linux操作系统的最新线程库。
多线程编程是一种比多进程编程更灵活,更高效的编程方法。在
Linux系统中提供了一个以POSIX为标准定义的线程(简称:POSIX
线程)为中心的各种系统调用。POSIX也是go并发编程在linux系
统下真正使用的内核接口,也就是NPTL本地POSIX线程库。
二、什么是线程
线程可以视为进程中的控制流,一个进程中至少要包含一个线程。
因为一个进程中至少要一个控制流持续运行。所以第一个线程会随着
其进程的启动而创建,这个线程也被称之为主线程。
一个进程中可以存在多个线程,这些线程是由当前进程中存在的线
程创建出来的,而创建的方法就是系统调用。更准确的说是调用
pthread_create函数。
拥有多个线程的进程可以并发执行多个任务,并且即使某个线程被
阻塞也不会影响到进程中其它任务的执行。线程会大大提升程序的响
应效率和吞吐量
线程不能独立于进程之上,线程的生命周期不能超过其所在进程的
生命周期。
三、如何实现一个线程
通过系统调用来及制线程。线程与进程的父子家族关系树结构不
同,线程之间的关系都是平等的。它们之间不存在层级关系,任何线
程都可以对同一进程中的其它线程进行有效的管理。其中管理分为5
种:
pthread_create创建线程
pthread_cancel终止线程
pthreadjoin连接已经终止线程
pthread_detach分离线程
pthread_exit彻底退出主线程
3-1V线程TID标识
和进程一样,线程也有属于自己的ID,叫做TID。但与进程不同
的是,线程ID在系统内中并不唯一,只会在其当前进程下唯一。不
过linux系统则确保了每个线程在系统内ID的唯一性。且当某个线
程不复存在后,其TID可以被其它线程复用。线程ID由操作系统内
核进行分配和维护。
四、线程运行时发生异常会怎么样
线程异常后会强制调用pthread.cancel函数来终止线程,然后
调用detach函数分离线程。根据不同的异常情况会进入僵死状态和
终止状态两种。
僵死状态就是还会保留一些程序所需的资源,不会删掉线程上的
全部数据。如果不再需要僵死线程,就会调用pthreadJoin终止掉
僵死线程。
五、如何在两个线程间共享数据
一个进程中的所有线程都拥有自己的线程栈,并以此来存储线程私
有的数据栈。这些线程的线程栈包含在其所属进程的虚拟内存空间当
中。
一个进程中的很多资源都会被其所有线程共享,这些被线程共享资
源包括(在当前进程的虚拟内存中存储的):数据段、代码段、堆、
栈、信号,以及当前进程支持的文件描述符。
正因如此,同一个进程中的多个线程运行的肯定是一个程序。只不
过具体的流控方式会有所不同。另外,创建一个线程也不会像创建进
程那样费劲,因为线程需要的所有代码、数据段、资源都在其进程中
存储不需要被复制就能使用。
操作系统内核提供了若干系统调用以便应用程序能够管理当前进
程中的所有线程,还可以通过相应的系统功能协调这些线程的运行。
六、线程状态
线程状态分为:
1.就绪状态
2.运行状态
3.阻塞状态
4.睡眠状态
当调用pthread_create函数时线程会进入就绪状态。当线程获
得运行时机被CPU激活运行之后会进入运行状态。
在线程运行中如果出现了阻塞等待,就会进入睡眠状态,阻塞解
除后会重新进入就绪状态。
在线程运行中如果出现了return语句,或者退出线程的语句,根
据当时不同的线程执行环境会进入僵尸状态和终止状态。
不管是僵尸状态还是终止状态最终都会被回收。
七、线程调度
在线程的生命周期中,操作系统内核对线程的调用是非常核心的部
分。正是因为有了调度器的实时调度和切换,才给众多线程一种并行
运行的幻觉。调度器会把事件划分成极小的时间片,并把这些时间片
分配给不同的线程,以使众多线程都有机会在CPU上运行。一个线
程什么时候能够获得CPU时间,以及在CPU上运行多久,都是调
度器的工作范畴。
调度器:最大程度保证多核CPU之间的平衡运行。
7-1V什么是线程调度,什么是线程切换
线程调度(也称线程间的上下文切换),线程的执行总是趋向于CPU
受限或者I/O受限。也就是说线程的调度只分为两类:一些线程需
要花费一定的时间使用CPU进行计算,另一些线程会花费一些时间
等待相对较慢的I/O操作完成。
调度器会依据它对线程的趋向性的猜测把他们进行分类,并让I/O
受限的线程具有更高的动态优先级以及优先使用CPU。调度器会
认为I/O操作往往会花费更长的时间,所以应该让它们尽早执行。
这也是为了让众多线程运行的更加高效。在人决定下一个要敲击的
按键、磁盘在磁道中定位簇或者网卡从网络中接收数据帧的时候,
CPU可以腾出手来位其它线程服务。
7-2v线程的静态、动态优先级调用
线程的动态优先级是可以被调度器实时调整的,而与之相对应的线
程的静态优先级只能由程序指定。如果应用程序没有指定一个线程的
静态优先级,那么默认为0。调度器不会改变线程的静态优先级。
线程的动态优先级就是调度器在其静态优先级的基础上调整得出
的,动态优先级决定了线程的运行顺序。
而线程的静态优先级决定了线程单次在CPU上运行的最长时间,也
就是调度器分配给它的时间片的大小。
所有等待使用CPU的线程会按照动态优先级从高到低的进行顺序
排序,并依序放到与该CPU对应的运行队列当中。因此,下一个运
行的线程总是动态优先级最高的那一个。
7-3.线程的优先级队列
每一个CPU的运行队列中都包含两个优先级阵列。其中一个用于
存放正在等待运行的线程,暂时称之为(激活的优先级阵列)。另一
个则用于存放正在等待的运行的线程,暂时称为(过期的优先级阵
歹U)。
下一个运行的线程总是会从激活的优先级队列中选出。如果CPU
发现某个线程占用了CPU很久的时间,并且激活的优先级队列中还
有优先级与他相同的线程在等待运行,那么调度器就会让那个等待的
线程在CPU上运行,而被换下的则进入过期的优先级阵列。
当激活的优先级阵列中没有等待运行的线程时,调度器会把这两个
优先级阵列的身份互换,之前的激活阵列变成过期阵列,现在的过期
阵列变成激活阵列。如此,放入过期的优先级阵列的线程就又有机会
运行了。
当然,线程并不是总会处于运行和就绪状态。他还有可能会进入阻
塞睡眠状态,处于睡眠状态的线程是不能够被调度和运行的。它们会
从两个阵列中移除。
七、线程模型
线程的实现模型分为三个,分别是:用户级线程模型、内核级线程模
型、两级线程模型。
它们之间最大的差异就在于线程与内核实体(内核调度实体KES)
之间的对应关系。内核调度实体就是被内核调度器所调用的实体对
象。也被称为内核级线程,是操作系统得最小运行单元。
1.用户级线程模型(M:l):
此模型下的线程是由用户级别的线程库全权管理的。线程库并不是
内核的一部分,而只是存储在进程的用户空间之中,这些线程的存在
对于内核而言是无法感知的。
用户级线程并不是内核的调度器的调度对象。对线程的各种管理和
协调完全是用户及程序的自主行为,与内核无关。应用程序在对线程
进行创建、终止、切换或同步等操作的时候,并不需要让CPU从用
户态转变成内核态。
2.内核级线程模型(1:1):
该模型下的线程是由内核负责管理的,它门是内核的一部分。
应用程序对线程的创建、终止和同步都必须通过内核提供的系统调
用来完成(pthread」)。进程中的每一个线程都会与一个KES(内核
调度实体)相对应。也就是说,内核可以分别为每一个线程进行调度。
由此,内核级线程模型也被称为1:1的线程实现。一对一线程实现
消除了多对一线程实现的很多弊端。可以真正的实现线程的并发运
行。
3.两级线程模型(M:N):
两级线程模型的目标是取前两种模型的精华,去其糟粕,也成为多对
多的(M:N)线程实现。
与其他模型相比,两级线程模型提供了更多的灵活性。在此模型
下一个进程可以与多个KSE(内核调度实体)相关联,这与内核级
线程模型相似,不同点在于进程中的线程并不与KES——对应,
这些应用程序线程可以映射到同一个已经关联的KES上。
实现了两级线程模型的线程库,会通过操作系统内核创建多个内
核级线程。然后它会通过这些内核级线程对应用程序线程进行调度。
大多数此类线程库都可以将这些应用程序线程动态地与内核级线程
关联。这样的设计显然使线程的管理工作更加复杂,因为这需要线程
库与内核级线程共同努力和协作才能正确、有效的运行。
八、线程同步
线程同步的目的就是为了更好地协同工作或者维持数据的一致性。
1.共享数据的一致性
一个进程所拥有的相当一部分虚拟内存地址都可以被进程中所有
线程共享,所以这些共享数据大多是以内存的空间作为载体。如果两
个线程同时读取同一块共享内存但获取到的数据却不相同,那么程序
很有可能就会出现某种错误。
这是因为,共享数据的一致性往往代表着某种约定,而只有在该约
定成立的前提下,多线程程序中的各个线程才能够使相应的流程执行
正确。换句话说,妇果操作的共享数据的结果,总是与约定的结果相
同,就说明共享数据的一致性得到了保证。
实际上,保证共享数据一致性的最简单最彻底的方法就是使该数据
成为一个不变量。例如:常量就是不变量,它不可能被改变,也就不
可能出现不一致的情况。因此无论当前程序中有多少个可能访问常量
的线程,都不需要采取任何措施。但是程序中不可能都是常量,我们
需要通过额外的手段保证被多个线程共享的变量的一致性,这样就有
了临界区的概念。
2.临界区
临界区是只能被串行化访问或执行的某个资源或者代码,也被称为
串行区域。保证临界区有效的最佳方式就是利用同步机制。在针对多
线程程序的同步机制中包含了很多同步方法,包括原子性操作和互斥
量,以及条件变量。
3.互斥量
在同一时刻,只允许一个线程处于临界区之内的约束,称之为互斥
(mutex)o
每个线程在进入临界区之前,都必须先锁定某个对象,只有成功
锁定对象的线程才会允许进入临界区,否则就会阻塞,这种对象被称
为互斥对象或互斥量。
互斥量有两种状态,既锁定状态、未锁定状态。互斥量每次只能
锁定一次,处于已锁定状态的互斥量不能被再次锁定。除非它已经解
锁,否则任何线程都不能对它进行二次加锁。如果对一个已锁定的互
斥量进行加锁操作,那么这个操作必定会失败。成功锁定互斥量的线
程会成为该互斥量的所有者,只有互斥量的所有者才能对其进行解
锁。从这个角度讲,多个线程对同一个互斥量的争相锁定也可以看作
是对互斥量的释放。
当线程离开临界区的时候,必须要对相应的互斥量进行解锁。这
样其它想进入该临界区而被阻塞的线程才会被唤醒,并且有机会再次
尝试锁定该互斥量。在这些线程中只有一个线程会成功锁定该互斥
量。注意:对同一个互斥量的锁定与解锁应该成队出现(defer)。
4,线程安全性,怎么实现线程安全性?
如果有一个代码块,它可以被多个线程并发执行,且总能够产生预
期的结果,那么该代码块就是线程安全的。
如果代码块对共享数据进行了更新操作,那么这个代码块就是非线
程安全的。但是如果该代码块处于临界区中,那么这个代码块就是线
程安全的。
但是,为了实现线程安全,把所有的代码都置于临界区中虽然可行,
但也是一种最低效的方法。可以仔细从函数体中查找出操作共享数据
的代码并用互斥量将它们保护起来。还可以将这写代码从函数体中分
离出来,然后再将它们聚集在一起成为一个函数或者一个结构体。为
这个函数或结构体实现线程安全性。
GMP
一、早期的GMP调度器
1.当内核线程获取g。的协程时,会经过调度器访问go的全局协程
队列。go的全局协程队列是由锁来进行保护的。当拿到队列的锁之
后,会执行队列中的首个goroutine协程,其余的goroutine依次向
前挪动一位。
2.内核执行完goroutine协程任务之后,会释放锁,并把这个goroutine
还回全局协程队列,只不过这时候会把goroutine放在队尾。
早期调度器的弊端:
L创建、销毁、调度G都需要每个内核线程M获取锁,形成了激烈
的锁竞争。
2.内核M转移goroutine,会造成延迟和额外的系统负载。
3.系统调用(CPU在多个M之间切换)导致频繁的线程阻塞和取消阻
塞操作,增加了系统开销。
二、GMP模型简介
GMP是三种元素的缩写。M与P是相互引用的关系,P与G是一对
多的调用关系。
G:goroutine协程,go中的代码片段,协程程序。
P:processor处理器,一个P代表执行了一个G。代码片段中所必需
的资源(上下文环境)
M:Machine,一个M代表一个内核线程,或者称为工作线程。
简单来说,一个协程(G)的执行需要P(处理器)和M(内核)的支
持,一个M在与P关联之后,就形成了一个有效的协程(G)运行环
境(内核线程+上下文环境)。每个P都会包含一个可运行的协程(G)
队列(runq)。该队列中的协程(G)会被依次传递给本地的P关联
M,并获得运行。
2-1x详解M
L什么情况下会建立一个M
一个M代表一个内核线程。大多数情况下,创建M都是由于没有
足够的M来关联P,并运行其中可运行的Go在系统执行系统监控和
垃圾回收等任务的时候,也会创建M。M是一个结构体。
2.M与P和G是如何关联的
M是一个结构体,这个结构体中包含了关联P,G的字段。
typemstruct{
go*g
curg*g
ppuintptr〃当前与M关联的P
nextppuintptr〃潜在关联的P
spinningbool〃表示M是否正在寻找可以运行的G。在寻找
过程中M会处于自旋状态。Go在运行时可以讲一个M和一个G锁
在一起,一旦锁定,这个M就只能运行这个G。
lockedg*g〃表示与M锁定的G
3.M是什么时候被创建的如何创建的?
在M被创建之后,GO系统会先对它进行一番初始化,其中包括对自
身所持有的栈空间以及信号处理方面的初始化。
M在创建之初,M会被加入全局的M列表中。这时它的起始函数
和预联的P也会被设置。当运行时,系统会为这个M专门创建一个
新的内核线程并与之关联。如果M就做好了执行G的准备。
起始函数只有当M执行系统监控和垃圾回收任务的时候才会被设
置。全局M队列在运行时系统需要的时候,会通过全局M队列,获
取到所有M的信息,同时防止M被当成垃圾回收掉。
4.空闲M,M停止会如何?
运行中的M有时也会被停止,比如在执行垃圾回收任务的过程中。
运行时系统在停止M的时候,会把它放入调度器的空闲M列表。这
很重要,因为在需要一个未被使用的M时,调度器会先尝试从空间
的M中获取。M是否空闲,仅以它是否存在于调度器的空闲M队列
表中为依据。
5.能否设置M的最大数量?
单个Go程序所使用的M的最大数量是可以设置的,Go程序运
行的时候会先启动一个引导程序,这个引导程序会为其建立必要的环
境。在初始化调度器的时候,它会对M的最大数量进行初始设置,
这个初始值是10000,也就是说一个Go程序最多可以使用10000个Mo
22详解P
1.什么是P
P是能够在M口运行的关键。GO的调度器会适时地让P与不同
的M建立或者断开关联,以使P中那些汇运行的G能够及时获得运
行时机,这与操作系统内核在CPU之上实时的切换不同进程或线程
的情形类似。
P的最大数量实际上是对程序中并发运行的G的规模的一种限制。
P的数量即为可运行G的队列的数量。一个G在被启用,会先追加到
某个P的可运行队列中,以等待运行。
一个P只有与M关联在一起,才会使其可以运行G。
2.P的数量,如何改变P的数量
在g。程序启动之初,引导程序会在初始化调度器时,对P的最大
数量进行设置。这里的默认值会与当前CPU的总核心数相同。一旦
发现环境变量COMAXPROCS的值大于0,引导程序就会认为我们想
要对P的最大数量进行设置。
它会先检查一下比值的有效性;如果不大于预设的硬性上限值(256),
就会认为是有效的,否则就会被这个硬性上限值取代。
第一种方法:调用函数PROCS把想要设定的数量作为参数传入。
第二种方法:在G。程序运行前设置GOMAXPROCS的环境变量值。
3.P脱离运行状态
虽然Go并未对何时调用PROCS函数作限制,但是该函数调用的执
行会暂时让所有的P都脱离运行状态,并试图阻止任何用户级别G
的运行。
只有在新的P最大数量设定完成之后,调度器才会陆续恢复它们。
这对于程序的性能是非常大的消耗。所以,P的数量最好在init()函
数里设置。实际上多数情况下不改变也没什么。
4.P的空闲列表
与空闲的M列表类型,G。中也存在一个空闲的P列表。当一个P
不再与任何M关联的时候,调度器就会把它放入该列表;而当调度
器需要一个空闲的P关联某个M时,就会从此列表中取出一。
进入空闲P列表的前提是,该P中可运行的G必须为空。
5.P的运行状态
Pidle:此状态表明当前P未与任何M存在关联。
Prunnig:此状态表明当前P正在与某个M关联。
Psyscall:此状态表名当前P中的运行的G正在进行系统调用。
Pgcstop:此状态表明调度器需要停止调度。开始垃圾回收。
pdead:此状态表明当前P已经不会再被使用。如果Go程序运行的
过程中,通过调用PROCS函数减少了P的最大数量,那么多余的P
就会被运行时系统置于此状态。
P在创建之初是pgcstop,但是这并不意味着要垃圾回收。当P进行
初始化之后会进入pidleo
6.P的自由调度列表,如何提高G的复用率
每个P除了有一个可运行的G队列外,还有一个自由G列表。这个
列表中包含了一些已经运行完成的Go
当go语句要启用一个G的时候,调度器会先试图从相应P的自由
G列表中获取一个现成的G,来封装这个go语句携带的函数。
如果自由G列表中的G太少,调度器会从可运行G列表中转移一
部分到自由列表中。如此,只有自由G列表中的G也没了的时候,
调度器才会创建新的G,最大可能的提高G的复用率。
23详解G
1.什么是G,goroutine的调度原理
一个G就是一个goroutine,编程人员使用go,gofunc向系统中提
交并发任务。
g。的编译器会把提交的g。语句变成内部函数newproc的调用,并
把g。函数以及参数都作为参数传递给这个函数。
调度器在接收到newproc这样一个调用后,会先检查go函数的合法
性,然后试图从本地的自由G列表与运行G列表中获取一个可用的
Go如果没有,则新建一个G。
与M和P相同,G也有一个全局列表c新建的G会第一时间加入
全局G列表。这个列表记录着全局中所有的G指针。
在初始化完成后,这个G会被存储到本地P的runnext字段中,该
字段用于存放新的G,处于这个字段的G会被更早地运行。如果这时
的runnext字段已经有了一个G,那么这个G就会被“踢到”该P的
可运行G队列的末尾。如果P的可运行队列满了,那么就只能追加到
调度器的可运行G队列中等待运行。
2.G的运行状态
Gidle:表示G刚被分配,还没有初始化
Grunnable:表示当前G正在可运行队列中等待运行。
Grunning:表示当前G正在运行。
GsysCall:表示当前G正在执行某个系统调用。
Gwaiting:表示当前G正在阻塞。
Gdead:表示当前G正在闲置。
Gcopystack:表示当前G的栈区正在移动。移动的原因可能是栈的扩
展或者收缩。
在运行时调度器用一个G封装goroutine函数时,会先对这个G进行
初始化,一旦G准备就绪,其状态就会被设置成Grunnableo一个G
真正被使用一定是在Grunnable之后。
三、GMP调度模型的策略
Go语言中的调度,是用来调度操作系统内核之外的程序。调度的对
象是MPG实例。
1.调度器的基本结构
调度器有自己的数据结构,形成此结构的主要目的就是更加方便的
管理和调度各个核心元素的实例,空闲M列表,空间P列表,可运
行G队列和自由G队列。
还有一些其它的重要字段:
1.重要字段
gcwaitinguint32表示是否需要因一些任务而停止调度。
stopwaitint32表示需要停止但仍未停止的P的数量
stopnodenote用于实现与stopwait相关的事件通知机制
sysmonwaituint32表示在停止调度期间系统监控任务是否在等待
sysmonnotenote用于实现与sysmonwait相关的事件通知机制
在g。调度器执行工作中,一些任务在执行前是需要暂停调度的,例
如垃圾回收任务中的某些子任务,又比如发起运行时恐慌的panic任
务。
2.gcwaiting>stopwait、stopnote都是串行运行时任务执行前后的辅
助协调手段。
Gcwaiting^topwait,stopnote:
该字段的值用于表示是否需要停止调度,在停止调度时,该值被设
置为1;在恢复调度时,该值被设定为0。
当一些调度任务在执行时只要发现gcwaiting的值为1,就会把当
前P的状态设置为pgstop,然后逐渐递减stopwait字段的值,当
stopwait字段的值递减为0时,就说明所有的P的状态都是pgstop,
这时就可以唤醒停止调度的任务了。
3.字段sysmonwait和sysmonnote针对的是系统监测任务。
sysmonwait>sysmonnote
在停止调度任务执行之前,系统监测任务也需要暂停。sysmonwait
就是表示是否已经暂停,0表示未暂停,1表示已经暂停。系统监测
任务是持续执行的,处于无限的循环当中。
每次调度器执行任务之初,系统监测程序都会先检查调度情况,
一旦发现调度停止gcwaiting字段不=0,或者P都已经处于闲置状态。
就会把sysmonwait的字段设置=1,并利用sysmonnote停止调度器自
身。
在恢复时会将sysmonwait设置=0,sysmonnote用来恢复系统监测
任务。
2.调度策略
全力查找:
调度器会为P来寻找可运行的G,如果没有找到就会进入全力查找
的状态,从各处搜索可以运行的G。如果当前P中实在找不到,就会
通过workstealing从别的P偷一个。
全力搜索的范围(了解即可)
1.获取已经终结准备回收的G
2.从本地P的可运行G列表中获取
3.从全局所有P的可运行G列表中获取
4.从调度器的可运行G列表中获取
5.从网络l/Onetpoller获取G
如果经历这些步骤还是没有获取到G,就会停止掉当前的M。当之后
的某一个时刻出现G之后,M才会被唤醒。
偷取机制:
当当前M关联的P中没有可运行的G时,会尝试从其它M关联的
P中偷取一个G放到当前M下运行,而不是销毁线程。
分离机制:
当当前M线程因为G进行系统调用发生阻塞时,该M线程会释放
绑定的P,并把整个关联的P转移给其它空闲的M线程进行关联运
行。
并行限制:
设置GOMAXPROCSP的数量,最多有GOMAXPROCS个线程分布在多
个CPU上同时运行,
3.抢占
抢占也叫系统监测,由sysmon函数实现。主要监测以下任务:
L在需要时抢夺符合条件的P和G,抢占模式
2.在需要时进行强制GC,清扫堆
3.在需要时打印调度器的跟踪信息
抢占P和G的途径有两个,首先是通过网络I/O轮询其获取可运
行的Go其次是从调度器那里抢夺符合条件的P和Go
抢占P过程:
在抢占P的流程中,全局P列表中的所有P都会被检查。程序会
先查看P的状态,如果为系统调用和运行状态,程序就会对P进行下
一步检查,
调度器会检查它的调度计数是否同步。P的调度计数由它的
schedick字段存储。只要它的可运行G队列中的某个G被取出运行了,
该字段值就会递增。
同样的在调度器中也会持有一个调度计数的备份,判断这个备份
与P中的schedick字段值是否相同。如果不同,就会忽略对这个P的
进行一步检查。
如果相同,就判断距离上次同步该P的调度时间是否满足10ms,
如果超过,救说明该P的G已经运行了太久,需要停止并把运行机会
让给其它的G。
总结:
1.首先从网络I/O中抢到要执行的Go
2.然后调度器会监测每一个P中的schedick字段,然后与调度器的
备份数据比较,如果相同。就会继续判断距离上一次同步该P的时
间是否超过10ms,如果超时就会抢占一个新的P来运行。
四、gofunc()都经历了哪些过程
1.创建一个gofunc()后,会把go语句编程对内部函数newproc的调
用,并把g。函数以及其参数都作为参数传递给这个newproc函数。
2.当系统接收到这样一个函数调用后,会检查go函数以及其参数的
合法性。与M和P相同,运行时系统也会持有一个G的全局列表,
新建的G会在第一时间被加入该列表。这个全局列表的作用就是集中
存放当前运行时系统中的所有G指针。
3•然后初始化go函数变成一个G,设置该G的状态和IDo当初始化
结束后就会将G存储到本地的P中等待运行。
4.如果当前没有跟M建立管理的P,则会放入全局队列中等待出现与
M相关联的P出现。
5.当M执行某一个G时发生了阻塞操作,M会阻塞,如果当前有一
些G正在运行,就会把这个M中的P剔除。然后再创建一个新的M,
或者全局M列表中拿到一个空闲的M来服务这个Po
五、调度器的生命周期,mO,gO
MO
L启动进程后的执行的第一个线程,也是编号为0的主线程。
2.在全局变量runtime.mO中,不需要在heap上分配。
3.负责执行初始化操作和启动第一个G的
4.当启动第一个G之后,M0就和其它M一样了。
GO
每次启动一个M,都会立刻创建第一个goroutine,就是gO
gO仅用于负责调度其它G
gO本身不指向任何可指向的函数。
每个M都会有一个自己的gOo
在调度或系统调用时,会使用M切换到gO来进行调度。
M0的gO放在全局空间
六、Netpoller
网络I/O轮询器,在操作系统提供的异步I/O基础组件之上,实现Go
自己的阻塞式I/O而编写的子程序。
当一个G试图在一个网络连接上进行读写操作的时候,底层程序
就会开始为此准备,这时这个G就会进入阻塞状态。
一旦G准备就绪,就会让netpoller立即通知为此等待的G,返
回给它相应的事件。
因此,从netpoller处获取G的意思,就是获取那些已经接收到
通知的G,此时这个G就可以进行网络读写操作了,调度器会让它进
入Grunnable准备状态,并等待运行。
七、M自旋
在GMP中,M自旋是代表M的一种工作状态。M处于自旋状态,
意味着M还没有找到要执行的Go这种状态需要循环持续监听是否
有G出现在全局队列。
如果M停止,就会退出自旋状态。一般情况下,调度器中至少要
保证一个M处于自旋。
如果发现调度器中没有自旋M了,就会创建一个新的M来启动Go
当创建或者恢复启动M时,M会自动进入自旋状态。
Coroutine
一、goroutine什么时候会发生阻塞?
场景1:由于原子、互斥量或通道操作调用导致Goroutine阻塞,调
度器将把当前阻塞的Goroutine
场景2:由于网络请求和10操作导致Goroutine阻塞,这种阻塞的
情况下,我们的G和M又会怎么做呢?
Go程序提供门网络轮询器(NetPoller)来处理网络请求和10操作
的问题,其后台通过kqueue(MacOS),epolKLinux)«£iocp(Windows)
来实现10多路复用。
场景3:当调用一些系统方法的时候,如果系统方法调用的时候发生
阻塞,这种情况下,网络轮询器(NetPoller)无法使用,而进行系统
调用的Goroutine将阻塞当前M。
场景4:如果在Goroutine去执行一个sleep操作,导致M被阻塞
了。
场景5:channel只写没有接收的时候会出现阻塞,代码层阻塞。
系统调用阻塞
当M因为系统调用而阻塞,G进入系统调用的时候,调度器会把
该M和P分离开。这时,如果这个P的可运行G队列中还有未被运
行的G,那么调度器就会找到一个空闲的M,或创建一个新的M,并
与该P关联,以满足这些G的运行需要。因此M的数量一般都要比
P多。
而那个阻塞的G系统调用,会与运行它的M锁住,直到阻塞结束,
才会释放锁。这时将M重新归入空闲的M队列,等待下一次的调用。
执行等待操作进入阻塞
如果goroutine的go代码中包含对channel通道值得等待操作,那
么在执行到对应代码得时候,这个G就会进入阻塞状态。需要等待从
通道类型值中接收数据。
此外,操作定时器,sleep函数也会造成G得等待。在事件到来之
前一直会处于阻塞。直到事件到来之后,G才会被唤醒,并转换至等
待运行得状态。
二、goroutine有几种状态?自旋状态是什么?
goroutine的状态就是G的状态,分为:初始化、等待运行、正在
运行、系统调用、阻塞、闲置、栈移动(回收)状态。
goroutine的自旋是M内核线程的自旋。在M的结构体中有一个
spinning的字段,该字段用来表示M是否正在寻找可运行的
G(goroutine)o在寻找过程
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 统编版语文六年级下册第16课《表里的生物》精美课件
- 稻谷种植与农产品市场分析考核试卷
- 秋天的早晨初三语文作文
- 描写雨的初三语文作文
- 拒绝平庸的初三语文作文
- 体育表演艺术培训与指导考核试卷
- 畜产品加工与畜产品质量安全控制考核试卷
- 矿山石材的开采对地貌影响考核试卷
- 搪瓷喷漆房通风系统考核试卷
- 三年级数学脱式计算题
- 《中国糖尿病防治指南(2024版)》解读
- 糖尿病患者的足部护理
- 土地流转合同补充协议书
- 七年级语文下册《登幽州台歌》课件
- 兼职劳务协议合同模板
- 2025-2030中国改性塑料市场盈利能力预测及发展机遇规模研究报告
- 2025全国国家版图知识竞赛题库及答案(300题)中小学组试题及答案
- 2025年河南机电职业学院高职单招语文2019-2024历年真题考点试卷含答案解析
- (二模)东北三省三校2025年高三第二次联合模拟考试 英语试卷(含答案解析)
- 静脉输液治疗的健康教育
- 2025-2030中国地面液压凿岩机行业市场发展趋势与前景展望战略研究报告
评论
0/150
提交评论