并发共享数据的一些思考
本文是关于并发共享数据的一些杂谈, 就是一时回忆起这方面的东西然后记录下来……
并发的竞争主要是涉及到数据的写操作,如果所有的线程单单是读操作,不会对共享数据产生竞争。
并发的访问共享数据,竞争的bug会随着时间和数据的规模越大而增大。很难排查,所以在开发并发性程序时,需要使用好并发的同步与互斥等复杂的方法来避免并发竞争,保护共享数据。
保护共享数据就是保护不变量被破坏的中间状态只有一个线程访问。
保护并发的方法:
- 对数据进行某种保护机制(例如 互斥量),当某个线程访问的时候,其他线程对该数据块只有已完成或者未开始的状态。
- 通过把对共享数据的操作设计为原子操作,不切分割的状态,让不变量保持稳定的状态。(无锁编程)
- 还有一种是使用事务的方式去处理数据结构的更新(类似数据库的事务提交),将需要做的操作存储在日志中,将之前的操作合为一步,再进行提交。当数据结构被另一个线程修改,后者处理已经重启的情况下,提交就不会进行。(软件事务内存)
互斥量:
使用c++标准库的互斥量,std::mutex,但是一般是使用具有RAII语法的std::lock_guard<std::mutex>来达到创建就自动锁住代码块,并在析构函数自动释放锁。
使用互斥量时,设计接口时需要注意不要留下对数据的任何访问能修改数据的能力,比如互斥量保护的代码块返回的是指针或者引用时,外部可能通过指针或引用来访问或修改不变量,那么就丧失了互斥量保护不变量的目的。
【切勿将受保护数据的指针或引用传递到互斥所的作用域之外,无论是函数返回值,还是存储在外部可见内存,或者以参数的形式传递到用户提供的函数中】
总的来说,并发编程时,注意成员函数的返回值和形参是否会传出被互斥量保护数据的指针/引用,或传入指针/引用来带出被互斥量保护数据。