【C++多线程】共享数据保护
保护共享数据的最基本的方式,是使用C++标准库提供的互斥量(头文件<mutex>)。当访问共享数据前,使用互斥量将相关数据锁住,再当访问结束后,再将数据解锁。线程库需要保证,当一个线程使用特定互斥量锁住共享数据时,其他的线程想要访问锁住的数据,都必须等到之前那个线程对数据进行解锁后,才能进行访问。这就保证了所有线程能看到共享数据,而不破坏不变量。
C++中通过实例化 std::mutex 创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 #include <list> 5 6 using namespace std; 7 8 class A { 9 public: 10 void input() 11 { 12 for (int i = 0; i < 1000; i++) 13 { 14 my_mutex.lock(); 15 cout << "加入数据:" << i << endl; 16 ilst.push_back(i); 17 my_mutex.unlock(); 18 } 19 20 } 21 22 void output() 23 { 24 for (int i = 0; i < 1000; i++) 25 { 26 my_mutex.lock(); 27 if (!ilst.empty()) 28 { 29 cout << "读读读读出数据:" << ilst.front() << endl; 30 ilst.pop_front(); 31 my_mutex.unlock(); 32 } 33 else 34 my_mutex.unlock(); 35 } 36 } 37 38 private: 39 list<int> ilst; 40 mutex my_mutex; 41 }; 42 43 int main() 44 { 45 A a; 46 thread t1(&A::input, &a); //注意此处需要传入的是对象地址,否则数据没办法共享 47 thread t2(&A::output, &a); 48 t1.join(); 49 t2.join(); 50 return 0; 51 }
C++标准库还为互斥量提供了一个RAII语法的模板类 std::lack_guard<T> ,其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁的互斥量总是会被正确的解锁。
1 #include <iostream> 2 #include <thread> 3 #include <mutex> 4 #include <list> 5 6 using namespace std; 7 8 class A { 9 public: 10 void input() 11 { 12 for (int i = 0; i < 1000; i++) 13 { 14 lock_guard<mutex> guard(my_mutex); 15 /*my_mutex.lock();*/ 16 cout << "加入数据:" << i << endl; 17 ilst.push_back(i); 18 /* my_mutex.unlock();*/ 19 } 20 21 } 22 23 void output() 24 { 25 for (int i = 0; i < 1000; i++) 26 { 27 lock_guard<mutex> guard(my_mutex); 28 /*my_mutex.lock();*/ 29 if (!ilst.empty()) 30 { 31 cout << "读读读读出数据:" << ilst.front() << endl; 32 ilst.pop_front(); 33 /*my_mutex.unlock();*/ 34 } 35 /*else 36 my_mutex.unlock();*/ 37 } 38 } 39 40 private: 41 list<int> ilst; 42 mutex my_mutex; 43 }; 44 45 int main() 46 { 47 A a; 48 thread t1(&A::input, &a); //注意此处需要传入的是对象地址,否则数据没办法共享 49 thread t2(&A::output, &a); 50 t1.join(); 51 t2.join(); 52 return 0; 53 }
但互斥量自身也有问题。
当其中一个成员函数返回的是保护数据的指针或引用时,会破坏对数据的保护。具有访问能力的指针或引用可以访问(并可能修改)被保护的数据,而不会被互斥锁限制。所以切勿将受保护数据的指针或引用传递到互斥锁作用域之外,无论是函数返回值,还是存储在外部可见内存,亦或是以参数的形式传递到用户提供的函数中去。
另外还会造成死锁,或是对数据保护的太多(或太少)的问题。
菜鸟手记。