美文网首页
第九章 基于共享变量的并发(二)Goroutine和线程

第九章 基于共享变量的并发(二)Goroutine和线程

作者: HaoR_W | 来源:发表于2018-01-17 14:44 被阅读0次

    一、动态栈(Growable Stacks)

    栈(stack):当前正在被调用或被挂起(旨在调用其他函数)的函数的内部变量(local variables)被存放在栈中。

    操作系统(OS)线程 goroutine
    类型 固定 动态
    大小 通常2MB 2KB~1GB
    特征 若线程所需内存较少,会造成浪费,比如需要大量功能简单的线程时线程数量会受到限制;
    若需要复杂或深层的递归调用,则可能会不够用。
    一般从2KB大小的栈开始生命周期,根据实际需要动态伸缩。

    二、Goroutine调度(Scheduling)

    OS内核调度

    OS线程由OS内核调度,每几毫秒,一个硬件计时器会中断处理器,这会调用(invoke)一个叫scheduler的内核函数进行线程调度

    scheduler函数:1. 挂起当前执行的线程并保存其寄存器的内容到内存中;2. 选择出下一个准备执行的线程,从内存中恢复其寄存器的数据,恢复执行该线程的现场并开始执行线程。

    缺点:线程的切换需要完整的上下文切换[1](包括 a. 保存一个用户线程的状态到内存;b. 恢复另一个线程的到寄存器;c. 更新调度器的数据结构)。这个操作很慢,因为相关数据在内存中的位置往往较分散(poor locality),需要大量的内存访问。

    Go的运行期调度(runtime scheduling)

    Go runtime包含自己的scheduler,其使用一种名为m:n调度(m:n scheduling)的技术。

    m:n调度(m:n scheduling):在n个OS线程上多工(multiplex)[2]调度m个goroutine

    Go runtime的scheduler的工作内容与OS的scheduler是类似的,不过只关注单独(single)Go程序中的goroutines(即某一个Go程序中的goroutine只会与该程序中的其他goroutine交换)。

    何时触发调度:不由硬件计时器触发,而是由一些Go语言的结构(constructs)隐式地触发。例如当一个goroutine调用了time.Sleep(),或者被channel或者mutex操作阻塞时,调度器会使其进入休眠并开始执行另一个goroutine。直到Sleep()时间结束或者channel或mutex的阻塞解除后再唤醒第一个goroutine。

    关于Go的线程调度是抢占式的还是协同式的:

    go在1.4版本加入了抢占式逻辑,之前的版本确实是非抢占式的,1.4以后版本的rutime sysmon会定期唤醒作系统状态检查,即使P处于阻塞的系统调也能被调用,不至于饿死,而且还检查某个G是否过多的占用了的cpu时间,并在某个时刻剥夺其cpu运行时间。[3]

    优点:这种调度方式不需要进入内核的上下文,所以调度一个goroutine比调度一个线程代价要低得多

    三、GOMAXPROCS

    GOMAXPROCS变量

    • 决定会有多少个操作系统的线程同时执行Go的代码(m:n调度中的n)
    • 默认值是CPU的核心数
    • 休眠中或者通信(communication)阻塞中的goroutine不需要对应的系统线程
    • 在被I/O或其他系统调用阻塞时,或调用非Go语言函数时,goroutine是需要一个对应的操作系统线程的,不过GOMAXPROCS不需要考虑这些情况
    • 修改方法:1. 修改环境变量GOMAXPROCS=n;2. 运行时调用runtime.GOMAXPROCS(n)函数

    最佳线程数

    最佳线程数与CPU核心数的关系并没有定论,得具体情况具体分析,原则是活跃线程数为 CPU(核)数时最佳[4]

    介绍CPU时有的会提到n核m线程,这里的m可以理解为CPU中单一核心支持的最大并行(parallel)线程数。

    描述CPU时所说的多线程:Intel的超线程(Hyper-threading)[5]技术,旨在充分利用CPU核心中的资源。简言之,假设有两个线程A和B,若A和B都(仅)需要核心中50%的某资源进行运算,则在应用了超线程的核心中,A和B两个线程可以同时进行运算(微观上的并行)。不过使用超线程技术并不一定意味着性能的提升,在一些情况下,性能甚至可能下降[6]

    四、Goroutine没有识别符(Identity)

    识别符带来的问题

    线程的身份信息会使得做一个抽象化的thread-local storage(线程本地存储,多线程编程中不希望其它线程访问的内容)变得很容易,比如一个以线程的id为key的map。而这可能会导致一个函数的行为不是仅由其参数,而还由其运行在的线程所决定

    Go鼓励更简单的编程风格

    影响函数行为的参数(parameters)都应被显式地指出。这样不仅使程序变得更易读,而且会让我们向一些给定的函数分配子任务时不用担心其身份信息会影响执行结果。





    1/16/18


    1. Context Switch

    2. 多工(多路复用)

    3. golang的goroutine调度到底是协作式的还是抢占式的?

    4. 多线程编程时,最佳线程数目与什么有关,核数,还有其他的吗? - Name5566的回答 - 知乎

    5. Hyper-threading

    6. 为什么 Intel 的超线程技术是一个核两条线程,而不是更多? - 不祥之刃的回答 - 知乎

    相关文章

      网友评论

          本文标题:第九章 基于共享变量的并发(二)Goroutine和线程

          本文链接:https://www.haomeiwen.com/subject/ymryoxtx.html