从单核CPU系统角度看并发问题

1,问题引入:

        在单核cpu系统中;进程有个全局量 int g_i = 0,在进程中开10个线程,每个线程都不对 g_i 加锁的情况下做1亿次自增操作 (g_i++) ;主线程等待所有的线程结束后,再打印 g_i 的值能保证是 10 亿吗?

2,问题初步思考:

        先考虑在多核cpu系统中,无锁情况下,两个cpu可能同时读取 g_i 到各自cpu寄存器,同时在各自寄存器中自增,然后写相同的值到内存;显然这里的自增操作有两次,实际值只是自增了一次,因此不能保证最终的g_i值是10亿。

        再考虑单核cpu系统,因为一个cpu只能运行一个进程(从linux内核角度线程也当做进程处理,这里不影响线程的讨论),那么问题中的10个线程必然是在单核cpu中交替调度运行的。单核cpu的并发可以理解为“伪并发”,而多核cpu系统中不同cpu的并行才是“真并发”。那么单核cpu线程调度会导致共享资源的不一致吗?或者是如何导致共享资源不一致的?

3,代码调试演示:

        先整个单核cpu系统环境(vmware设置linux系统cpu核数为1),

        3.1 先看不加锁情况,代码运行的结果;在到达稳态后,g_i 的值仅有 2 亿多.

                                                                                                                                 图1

         3.2 再看加锁情况,代码运行的结果;在到达稳态后,g_i 的值正好是10亿.

                                                                                                                                图2

为什么在单核cpu系统,不加锁会得出错误的结果? 

 4,结果分析

        4.1 反汇编

        将无锁版的可执行程序反汇编后,如下图所示。从汇编中也可以看到一些规则,比如未经初始化的全局变量,放在虚拟进程空间的 bss 区域. 子线程可以直接访问主进程的 bss 区域,实际上子线程能访问主进程的整个用户虚拟地址空间。包括:代码区域,data区域,bss区域,堆,所有共享库代码。此外,主进程栈空间能被子线程访问吗?实际上也可以通过指针参数传递给子线程来访问。

                

                                                                                                                                    图3

        4.2 分析汇编

        一个重要的概念是:一组运行在一个进程的上下文中的每个并发线程,都有其独立的线程上下文: 包括 线程ID,栈,栈指针,PC(%rip),条件码,通用目的寄存器(16个,%r[a-d]x,... %rbp,%rsp,%r8-15)。结合下图对@1,@2,@3 步骤逐个分析。

        @1:线程1执行 mov 0x200942(%rip), %eax  指令,0x200942(%rip) 是基址+偏移量寻址,因为 %rip (PC) 表示将执行的下一条指令的地址,结合图3,%rip 值是0x400714 的下一个指令地址 0x40071a,因此 0x200942(%rip) 表示的地址是 M[0x200942+0x40071a] = M[0x60105c];正好是 图3  的 bss 区域的 g_i 地址。该指令将 g_i 的值传送到 %eax 寄存器。

        @2:线程1执行完 mov 0x200942(%rip), %eax  指令后,被线程2抢占了。此时需要线程切换,线程切换内核需要保存被换出的线程的上下文(线程ID,栈,栈指针,PC(%rip),条件码,通用目的寄存器(其中就有%[r/e]ax))。一个问题是,如果此时线程1的%eax值是5000,那么经过@2、@3步骤切换回线程1,线程1在执行 add $0x1, %eax 之前,%eax的值是5000还是5001? 这个小问题是整个大问题的关键。

        @3:线程1被恢复执行,在恢复执行前,内核需要恢复线程1的线程上下文,其中就包括 %eax 寄存器的值。如果@2被抢占步骤线程1的%eax是5000,那么此时被恢复的%eax应该还是 5000 (这个和 线程2 的%eax由操作系统层面保证区分开来)。

        通过 @1,@2,@3 步骤的分析,以 0x200942(%rip) = g_i = 5000 为例,可以发现 线程1 被抢占后继续运行的结果是 5001,线程2 运行的结果同样也是 5001,虽然两个线程一共对 g_i 操作了两次自增,实际 g_i 的值仅自增了一次。由此得出的结论是:不管单核的“伪并发”还是多核的“真并发”,不加保护的操作共享资源都可能会导致资源不一致。

        

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值