• goroutine(1) go的调度器


    对多道并行执行的程序来说,有时它要占用处理器运行,有时要等待传送信息,当得到信息后又可继续执行,一个程序的执行可能受到另一个程序的约束。所以程序的执行实际上是走走停停的,为了能正确反映程序执行时的活动规律和状态变化,引进了进程,以便从变化的角度,动态地分析和研究程序的执行。

    用计算机系统来解决某个问题时首先必须编制程序,可把程序看作是具有独立功能的一组指令的集合,或者说是指出处理器执行操作的步骤。程序的执行必须依赖于一个实体-数据集

    还有一个问题就是从程序的角度无法正确描述程序执行时的状态。例:只有一个“编译程序”,同时为多个用户服务,有两个需要编译的程序甲、乙,假定编译程序p从a点开始工作,现在正在编译程序甲,当工作到b点时,需要把中间结果记录在磁盘上。于是程序p在b 点等待磁盘传输信息。这时处理器空闲,这时让p为源程序 乙进行编译,编译程序仍从a点开始工作。现在 的问题是如何来描述p的状态?是说“它在b点等侍磁盘传输”还是说“从a点开始执行?

    1.进程和线程

    进程:把一个程序在一个数据集上的一次执行称为一个进程

      (程序是静止的,进程是动态的。在20世纪80年代,大多数操作系统仍采用进程技术,把进程作为操作系统的基本组成单位。进程既是资源分配单位,又是调度和执行单位)

    线程:线程是进程中可独立执行的子任务。

      (把一个计算问题或一个应用问题作为一个进程,把该进程中可以并发执行的各部分分别作为线程。一个数据库应用程序的运行创建了一个数据库进程,用户要求从数据库中生产一份工资报表,并将它传送到一个文件中。用户在等待报表生成的时候又向数据库提出一个查询请求。操作系统把用户的每一个请求(工资单报表,数据库查询)分别作为数据库进程的一个线程)

    为什么引入线程?

      1.每个进程都要占用一个进程控制块和一个私有的主存空间,开销比较大;进程之间传递消息时要从一个工作区传到另一个工作区,需专用通信机制,速度较慢

      2.进程增多就增加了进程调度的次数,给调度和控制带来了复杂性。

      在采用线程的操作系统中,进程是资源分配 单位,而线程是调度、执行单位。

    协程:独⽴的栈空间,共享堆空间,调度由⽤户⾃⼰控制,本质上有点类似于⽤户级线程,这些⽤户级线程的调度也是⾃⼰实现的 。⼀个线程上可以跑多个协程,协程是轻量级的线程。

    进程——>一个线程——>单线程程序

    进程——>多线程——>多线程程序

    2.并发和并行

    1)多线程程序在cpu一个核蕊上运行就是并发

    2)多线程程序在cpu多个核蕊上运行就是并行

    并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。

    并发:

    并行:

     3.用户空间线程和内核空间线程之间的映射关系 

    N:1,   1:1和   M:N

    N:1是说,多个(N)用户线程始终在一个内核线程上跑,context上下文切换确实很快,但是无法真正的利用多核。

    1:1是说,一个用户线程就只在一个内核线程上跑,这时可以利用多核,但是上下文switch很慢。

    M:N是说, 多个goroutine在多个内核线程上跑,这个看似可以集齐上面两者的优势,但是无疑增加了调度的难度。

    4.go的调度器

    go 的高度器的内部结构:M P G

    M:代表真正的内核OS线程,和POSIX里的thread差不多,真正干活的人
    G:代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
    P:代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。
     

    图中看,有2个物理线程M,每一个M都拥有一个context(P),每一个也都有一个正在运行的goroutine。

    P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。

    图中灰色的那些goroutine并没有运行,而是出于ready的就绪态,正在等待被调度。P维护着这个队列(称之为runqueue),

    Go语言里,启动一个goroutine很容易:go function 就行,所以每有一个go语句被执行,runqueue队列就在其末尾加入一个
    goroutine,在下一个调度点,就从runqueue中取出(如何决定取哪个goroutine?)一个goroutine执行。
     
    为何要维护多个上下文P?因为当一个OS线程被阻塞时,P可以转而投奔另一个OS线程!
    图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。
    图中的M1可能是被创建,或者从线程缓存中取出。 当MO返回时,它必须尝试取得一个context P来运行goroutine,一般情况下,它会从其他的OS线程那里steal偷一个context过来,
    如果没有偷到的话,它就把goroutine放在一个global runqueue里,然后自己就去睡大觉了(放入线程缓存里)。Contexts们也会周期性的检查global runqueue,否则global runqueue上的goroutine永远无法执行。
    另一种情况是P所分配的任务G很快就执行完了(分配不均),这就导致了一个上下文P闲着没事儿干而系统却任然忙碌。但是如果global runqueue没有任务G了,那么P就不得不从其他的上下文P那里拿一些G来执行。一般来说,如果上下文P从其他的上下文P那里要偷一个任务的话,一般就‘偷’run queue的一半,这就确保了每个OS线程都能充分的使用。
    参考:https://www.zhihu.com/question/20862617


  • 相关阅读:
    Xsheel远程链接ECS,Xftp上传文件到ECS
    javaEE 转发 和 重定向
    android 快速开发框架
    java/Android String.split 字符串分割
    Android 选择本地图片的demo
    docker swarm 集群进入某节点容器失败的原因及解决方法
    nginx中有关 root 和 alias的主要区别
    docker研究-2
    docker研究-1
    mysql数据库表操作-表的主键索引和普通索引
  • 原文地址:https://www.cnblogs.com/wanghaijun999/p/8351862.html
Copyright © 2020-2023  润新知