1. 线程共享数据会造成什么问题?
1.1 读写不一致
多线程读不会造成数据变动,所以没有问题。只要有一个线程设计修改数据,就会导致数据共享出现问题,简单的是数据不一致,严重的是程序访问已经释放的内存,造成程序崩溃。
1.2 顺序不一致(竞态条件)
1.2.1 为什么要有顺序性问题?
因为,读写操作的不同,不同的访问顺序是是十分重要的。
1.2.2 造成顺序性问题的原因?
因为,需要改动多处数据,造成必须要保证改动多出数据时每一步的连续(一个线程多处改动,多次改动,都必须抢占cpu,不能允许其他线程在这个过程被其他线程修改)
1.2.3 如何防止恶性的条件竞争?
什么是恶性竞争?可以理解为破坏程序,导致程序崩溃的,读写不一致程序还能运行,如果释放指针,访问已经释放的地址导致内存崩溃。
防止恶性竞争的方式有:
- 修改数据时,每一步动作不被其他线程抢占。(常见的有:互斥锁)
- 使用事务,也就是原子操作。(mysql的事务,redis中lua脚本)
2. 互斥方式保护共享数据
2.1 互斥锁
通过std::mutex,然后调用lock和unlock函数。但是通过unlock,则需要在每一个访问共享数据的线程上都调用unlock,同时也要注意抛出异常的问题,所以采用了基于RAII机制的lock_guard()函数,构造时加锁,析构时释放锁。
缺点:一旦有暴漏的引用或者指针,那么访问加锁后,仍然能够被访问(可以推导出,加锁并不是锁内存,很可能就是锁变量的访问)。就是:你要访问共享变量,你就需要对所有的访问共享变量的环节都互斥锁,进行访问,不然的话如果通过指针或者引用去访问内存时,是无法解决这个问题的。
class some_data
{int a;std::string b;
public:void do_something();
};class data_wrapper
{
private:some_data data;std::mutex m;
public:template<typename Function>void process_data(Function func) {std::lock_guard<std::mutex> l(m);func(data); 1.受保护的线程安全的函数,如果多线程访问该函数的时候,就一定线程安全,因为使用了互斥锁。}
};some_data* unprotected;void malicious_function(some_data& protected_data)
{unprotected=&protected_data;
}data_wrapper x;void foo()
{x.process_data(malicious_function); 2.如果你某天添加功能,以引用或者指针的方式向外部调用恭喜那个变量,这个时候就是非线程安全了,因为malicious_function是非线程安全的。unprotected->do_something(); 以无保护方式访问本应受保护的共享数据
}