一、共享内存带来的问题
读时没问题,写时会有竞争问题。
二、解决方法
1、最简单的办法就是对数据结构采用某种保护机制,确保只有进行修改的线程才能看到不变量被破坏时的中间状态。从其他访问线程的角度来看,修改不是已经完成了,就是还没开始。
2、另一个选择是对数据结构和不变量的设计进行修改,修改完的结构必须能完成一系列不可分
割的变化,也就是保证每个不变量保持稳定的状态,这就是所谓的无锁编程。另一种处理条件竞争的方式是,使用事务的方式去处理数据结构的更新。(STM)
第二种是比较高阶的内容,不在讨论范围内。讨论第一种也就是使用互斥量保护数据。
三、std::mutex 创建互斥量
1、通过调用成员函数lock()进行上锁,unlock()进行解锁。不推荐实践中直接去调用成员函数,因为调用成员函数就意味着,必须记住在每个函数出口都要去调用unlock(。C++标准库为互斥量提供了一个RAII语法的模板类 std::lock_guard ,其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁的互斥量总是会被正确的解锁。#include <mutex>头文件
2、注意的问题
使用互斥量来保护数据,并不是仅仅在每一个成员函数中都加入一个 std::lock_guard 对象那
么简单;一个迷失的指针或引用,将会让这种保护形同虚设。不过,检查迷失指针或引用是
很容易的,只要1)没有成员函数通过返回值或者输出参数的形式向其调用者返回指向受保护数
据的指针或引用,2)成员函数没有通过指针或引用的方式来调用外部没有被保护的函数或者变量,数据就是安全的。
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)//传入函数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 传递一个恶意函数 unprotected->do_something(); // 3 在无保护的情况下访问保护数据,unprotected是some_date类型的 } //相当于在保护机制下,函数访问了一个没有被保护的外部函数.也就破坏了保护了
意味着foo能够绕过保护机制将函数 malicious_function 传递进func()!