多线程编程的锁问题解析(锁竞争死锁活锁及Date Race等)


本文是学习了 Guancheng 大神的文章后,根据文中的相关问题,进行代码分析,并且总结出这篇博客。

原文地址:http://blog.csdn.net/freeelinux/article/details/54091140

大神文章地址:http://www.parallellabs.com/2011/10/02/lock-in-parallel-programming/#comment-1245


在并行程序中,锁的使用主要会引发两类难题,一类是诸如死锁、活锁等引起的多线程 bug;另一类是由锁竞争引起的性能瓶颈。本文的分析主要是大神的分析,中间穿插我的验证以及总结,可以说是一篇 ”读博笔记“,可以直接点上方链接看原文。


1.用锁来防止 Data Race


在进行并行编程时,我们常常需要使用锁来保护共享变量,以防止多个线程同时对该变量进行更新时产生数据竞跑(Data Race)。所谓数据竞跑,是指两个(或多个)线程同时对某个共享变量进行操作,且这些操作中至少有一个(即>=1)是写操作时所造成的程序错误。例 1 中的两个线程可能同时执行 “counter++” 而产生数据竞跑,造成 counter 的最终值为 1(而不是正确值2)。

例 1:
#include <pthread.h>
int counter = 0;
void *func(void *params)
{
    counter++; //数据竞跑
}
void main()
{
    pthread_t thread1, thread2;
    pthread_create(&thread1, 0, func, 0);
    pthread_create(&thread2, 0, func, 0);
    pthread_join(thread1, 0 );
    pthread_join(thread2, 0 );
}
这是因为 counter++ 本身是由三条汇编指令构成的(从主存中将 counter 的值读到寄存器中;对寄存器进行加 1 操作;将寄存器中的新值写回主存),所以例 1 中的两个线程可能按如下交错顺序执行,导致 counter 的最终值为 1 。

例 2:
load [%counter], rax; // 线程1从counter读取0到寄存器rax
add rax, 1; // 线程1对寄存器rax进行加1
load [%counter], rbx; // 线程2从counter读取0到寄存器rbx
store rax [%counter]; // 线程1把1写入counter的主存地址
add rbx, 1; // 线程2对寄存器rbx进行加1
store rbx, [%counter]; // 线程2把1写入counter的主存地址
为了防止例1中的数据竞跑现象,我们可以使用锁来保证每个线程对counter++操作的独占访问(即保证该操作是原子的)。在例3的程序中,我们使用mutex锁将counter++操作放入临界区中,这样同一时刻只有获取锁的线程能访问该临界区,保证了counter++的原子性:即只有在线程1执行完counter++的三条指令之后线程2才能执行counter++操作,保证了counter的最终值必定为2。

下面是我的验证:
在我的机器上,我一开始没有查询 CPU 信息,执行例 1 中的程序,一直无法产生 Data Race,即便我把 counter++的次数改为 1000 次,开启 5 个线程执行也不行。后来我查看 CPU 信息,命令:cat /proc/cpuinfo ,发现我的 vagrant 中安装的 ubuntu 是单核的,我们知道,在单核 CPU 中,任意时刻只能有一个线程处于运行态。所以虽然启动了 5 个线程,实际上对 counter++ 的操作还是串行的。于是我修改了 vagrantfile,将 CPU 改为四核心,然后重新验证了例 1 的程序,发现在 counter++ 执行次数少的情况下,Data Race  出现的可能性很小(反正我的机器上执行两个线程各执行一次 counter++,得到的结果总是2),在执行次数较多的情况下,Data Race 的情况的有较大的概率复现。
验证代码如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <algorithm>

int counter = 0;
std::mutex mt; 

void thread_task()
{
//  mt.lock();
    for(int i=0; i<1000; ++i)
        counter++;
//  mt.unlock();
}

int main()
{
    std::vector<std::thread> workers;
    for(int i=0; i<5; ++i){
        auto t = std::thread(thread_task);
        workers.push_back(std::move(t));
    }   
    std::for_each(workers.begin(), workers.end(), std::mem_fn(&std::thread::join));
    std::co
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值