• 【协作式原创】查漏补缺之Go并发问题(单核多核)


    主要回答一下几个问题
    1.单核并发问题
    2.多核并发问题
    2.几个不正确的同步案例

    1.单核并发问题

    • 先看一段go(1.11)代码: 单核CPU,1万个携程,每个携程执行100次+1操作, 思考n最终会打印多少?
    package main
    import (
    	"fmt"
    	"time"
    	"runtime"
    	"sync"
    )
    var n int
    var wg sync.WaitGroup
    
    func main() {
    	runtime.GOMAXPROCS(1) //单核 
            // runtime.GOMAXPROCS(2) //多核 
    	wg.Add(10000)
    	for i:=0;i<10000;i++{
    		go add()
    	}
    	wg.Wait()
    	fmt.Println("累加结果:",n)
    }
    func add() {
    	for i := 0; i < 100; i++ {
    		n++
    		time.Sleep(1)
    	}
    	wg.Done()
    }
    //output 单核
    累加结果: 1000000
    //output 多核
    累加结果: 970820
    
    • 对比一段c语言多线程代码(单核运行),思考TestInteger会打印多少
    // 编译: gcc main.c -o main -plthread
    // 运行: ./main.exe
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    // 重定义数据类型
    typedef signed   int    INT32;
    typedef unsigned int    UINT32;
    // 宏定义
    #define THREAD_NUM     2              // 线程个数
    UINT32 g_iTestInteger = 0;
    // 函数声明
    void ProcessTask(void *pParam);
    int main(void) { 
        pthread_t MultiHandle  = 0;      // 多线程句柄
        UINT32    iLoopFlag    = 0;
        INT32     iRetVal      = 0;  // 创建线程函数的返回值
        // 循环创建线程
        for (iLoopFlag = 0; iLoopFlag < THREAD_NUM; iLoopFlag ++)
        {
            iRetVal = pthread_create(&MultiHandle, NULL, (void * (*)(void *))(&ProcessTask), (void *)iLoopFlag);
            if (0 != iRetVal)
            {
                printf("Create ProcessTask %d failed!
    ", iLoopFlag);
                return -1;
            }
        }
        Sleep(2000);    /* windows 使用Sleep,参数为毫秒 */
        printf("In main, TestInteger = %d
    ", g_iTestInteger);
        return 0;   
    }
    void ProcessTask(void *pParam){
        for (int i = 0;i<100;i++){
            g_iTestInteger ++;
            Sleep(1);    /* windows 使用Sleep,参数为毫秒 */
        }
    }
    //output
    In main, TestInteger = 198
    

    Q: 单核环境下,对于n++问题,go为什么没有并发问题,而c语言有并发问题?
    A:

    1. n++对应的汇编指令是3条.
      1.1 加载: 加载n到寄存器,
      1.2 更新: 更新寄存器(n+1)
      1.3 存储(写回内存): 把寄存器的值存储到内存中n对应的内存地址中
      参考<深入理解计算机系统第3版>12.3小节的图12-18b:

    2. c语言的多线程调度是抢占式的,多线程的上下文切换可以发生在任何指令之间(TODO除了少数原子指令)。
      所以c语言是有并发问题的。

    3. go的非抢占式调度携程, 上述代码只在函数调用时触发协程切换(go1.14版本以前,调用sleep时触发调用), 所以n++的3个指令可以一次执行完成,然后进入sleep才切换到另一个go携程,所以每个携程的n++是串行执行的,即使用1万个携程来测试也没有并发问题:

    0++
    sleep切换
    1++
    sleep切换
    打印2,退出
    

    总结:go在sleep时才发生协程切换,c语言的多线程切换可能发生在任何指令处,两者的切换粒度不一样。

    TODO:go1.11具体是怎么一个非抢占式度。

    Q: 为什么GO代码单核没有问题,多核有问题

    参考附录1

    1. 在单核CUP的情况下,每个CPU只会运行一个go携程,并且每个go携程执行时不会中断n++的指令,所以至少这些携程的n++语句是串行执行的,所以不会有并发问题。
    2. 在多核的情况下,多个CPU可以同时运行多个go携程,即使n++不被中断,但是由于多个携程同时读取相同的内存值,会出现后提交覆盖先提交的情况,所以会导致并发问题。
    您说的没错,我写了一段新的代码 https://play.studygolang.com/p/NkQhyGaMtnF 
    1. 这段代码在playground上是没有并发问题的。是因为playgound的执行环境是单核CPU,由于go携程是非抢占式调度的,所以每个时刻其实只有一个携程在执行CompareAndSwapInt64进行+1操作,所以是不会有并发冲突的。 
    2. 这段代码在本机的多核环境下运行是有并发问题的。因为每个时刻有多个CPU在同时执行多个go携程,那么就会有多个携程同时读到同一个G_Int的情况,在go携程把更新后的值写回内存时,就会发生Compare失败的情况。
    

    Q: 多核为什么有并发问题?

    A:

    对于go

    尽管携程是非抢占式调度的,但是如果有多核的话,就有多个P来同时执行携程。TODO

    对于c

    1. 同时多个cpu读取到了相同的n,后提交的线程会把先提交的线程的n++结果覆盖掉,导致部分线程加1操作丢失。

    Q: 加锁时如何解决c语言的多核多线程并发问题
    A:

    1. 锁的两种底层原理
      总线锁:
      缓存锁:

    https://studygolang.com/articles/18630

    1. window下搭建c语言运行环境
    2. vscode使用Code Runner插件运行程序
    3. C语言多线程中变量累加问题的分析

    TODO

    加锁的2种底层实现


    然后加锁操作的话,对应图中就是对cpu总线加锁,使得同一时刻只有一个cpu能访问内存。但是这个效率比较低,于是有了基于cpu缓存的锁。
    加锁的2种底层实现,我在这看的:https://mp.weixin.qq.com/s/RDEQSOjrSBVYVq6LV5MslQ

    Q: 问:如何实现x++的原子性?

    在单处理器上,如果执行x++时,禁止多线程调度,就可以实现原子。因为单处理的多线程并发是伪并发。
    在多处理器上,需要借助cpu提供的Lock功能。锁总线。读取内存值,修改,写回内存三步期间禁止别的CPU访问总线。同时我估计使用Lock指令锁总线的时候,OS也不会把当前线程调度走了。要是调走了,那就麻烦了。
    CPU中的原子操作

    参考资料

    1. golang CAS原子操作和单核,多核并发问题
  • 相关阅读:
    控件属性大全(持续更新)
    STM32F0使用LL库实现UART接收
    STM32F0 LL库IIC第二地址配置错误
    C# 抽象类小谈
    DevExpress 动态换肤
    DevExpress ChartControl大数据加载时有哪些性能优化方法
    Devexpress WPF ChartControl 多Y轴
    Lambda表达式的使用
    Prism--MVVM 之Command
    WPF Toolkit Chart--动态换列
  • 原文地址:https://www.cnblogs.com/yudidi/p/12298035.html
Copyright © 2020-2023  润新知