多线程:共享数据

共享数据

问题的引出
数据涉及到共享,就意味着有多个对象可以对其进行操作。如果操作时只读的,那么所有对象获得的数据是一样的,这样看起来是很安全的,因为哪个对象都改变不了数据。最麻烦是当对象可以对数据进行修改的时候,如何保证各个对象获取”正确“的值是一个很重要的问题。
在并发过程中,两个线程对同一个数据的进行操作,其操作的先后顺序不一样,可能导致程序运行的结果不一样;或者一个线程对数据的修改没有完成而另一个线程也在同时进行修改这,这种操作就有很大的导致程序崩溃的风险。对于这种恶性竞争使用共享资源的情况,有多种机制可以来避免其发生。
1)一种是对数据结构和不变量的设计进行修改,使其具有一些不可分割的特性,保证每个不变量处于稳定的状态,这就是无锁编程。
2)另一种是使用事务的方式去处理数据结构中的更新。对数据的操作都存储在事务日志中作为一步进行提交,一旦数据被其他线程修改,则无法提交运行,该过程称之为”软件事务内存“。

互斥量的使用
为了让共享数据在使用的过程中,不发生恶性竞争,可以共享的数据标记为互斥量,也就是同一时间只能有一个线程在对其进行操作。这就保证了只能在一个线程对共享数据操作完成之后才能让给另一个线程进行操作,避免了同时对数据进行操作的情况发生。这种占有共享数据的操作称之为上锁,意思就是这个线程锁定了该数据,其他线程不能访问操作;解锁是该线程释放该数据的占有权,可以让其他线程进行操作。

在C++中,通过使用std::mutex来创建互斥量,使用lock()成员函数来进行上锁,unlock()成员函数来进行解锁。但使用这种上锁解锁的方式,意味着在函数的入口和出口均需要调用相关的函数,甚至包括异常情况的发生也需要。因此,可以使用C++标准库提供的RAII语法的模板类:std::lock_guard,它会在自动管理上锁和解锁的操作,而无需使用者去关心何时执行相关操作。
#include <list>
#include <mutex>

std::list<int> some_list;    
std::mutex some_mutex;   

void add_to_list(int new_value)
{
  std::lock_guard<std::mutex> guard(some_mutex);  
  some_list.push_back(new_value);
}

具有访问能力的指针或引用可以访问(并可能修改)被保护的数据,而不会被互斥锁限制。切勿将受保护数据的指针或引用传递到互斥锁作用域之外,无论是函数返回值,还是存储在外部可见内存,亦或是以参数的形式传递到用户提供的函数中去。

即使在一个很简单的接口中,依旧可能遇到条件竞争。对于一个共享的类对象,每个成员函数都对其共享的数据具有操作权限,而因为其操作的顺序不一样会使的其他一些成员函数的所返回的值不再可靠。

死锁
死锁是两个不同线程互相等待,从而陷入死循环中。常见的方式就是一对线程对他们所拥有的互斥量做一些操作,其中每个线程都有各自的互斥量,且等待另一个解锁。
避免死锁的方法:
1)避免嵌套锁,就是在一个线程获得一个锁之后,不再去获得第二个所。
2)当需要锁住多个互斥量时:总是让两个互斥量以相同的顺序进行上锁,或者使用std::lock一次性锁住多个互斥量。
3)避免在持有锁的同时调用用户提供的代码。
4)使用锁的层次结构。

锁的粒度
锁的粒度是一个摆手术语(hand-waving term),用来描述通过一个锁保护着的数据量大小。一个细粒度锁(a fine-grained lock)能够保护较小的数据量,一个粗粒度锁(a coarse-grained lock)能够保护较多的数据量。
锁不仅是能锁住合适粒度的数据,还要控制锁的持有时间,以及什么操作在执行的同时能够拥有锁。一般情况下,执行必要的操作时,尽可能将持有锁的时间缩减到最小。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值