Effective Modern C++: 多线程与资源互锁 [一]
本文将介绍C++11中实现并发以及用mutex实现资源互锁。
先强化一下并发的概念, 下图介绍了一个计算机处理恰好两个任务时的理想情景,每个任务被分为10个相等大小的 块。在一个双核机器(具有两个处理核心)上,每个任务可以在各自的处理核心上执行。在单核 机器上做任务切换时,每个任务的块交织进行。但它们中间有一小段分隔(图中所示灰色分隔 条的厚度大于双核机器的分隔条);为了实现交织进行,系统每次从一个任务切换到另一个时都 需要切换一次上下文(context switch),任务切换也有时间开销。进行上下文的切换时,操作 系统必须为当前运行的任务保存CPU的状态和指令指针,并计算出要切换到哪个任务,并为 即将切换到的任务重新加载处理器状态。然后,CPU可能要将新任务的指令和数据的内存载 入到缓存中,这会阻止CPU执行任何指令,从而造成的更多的延迟。
c++11标准可以用std::thread实现多线程,多线程的启动有两种方式:
- join:等待线程完成,可以确保局部变量在线程完成后,才被销毁
- detach:主线程不能与之产生直接交互。也就是说,不会等待这个线程结束
Mutex:
当多个线程常出现恶性条件竞争时需要进行加锁,通常发生于完成对多于一个的数据块的修改时,例如,对两个连接指针的修改 (如图3.1)。因为操作要访问两个独立的数据块,独立的指令将会对数据块将进行修改,并且 其中一个线程可能正在进行时,另一个线程就对数据块进行了访问。
使用Mutex进行互锁:
(1)使用mutex的lock和unlock手动加锁和解锁
(2)使用lock_guard和unique_lock智能锁
详细内容看书《C++ Concurrency in Action》
示例代码:
1 #include <chrono> 2 #include <mutex> 3 #include <thread> 4 #include <iostream> 5 6 std::chrono::milliseconds interval(100); 7 8 std::mutex mutex; 9 int job_shared = 0; //两个线程都能修改'job_shared',mutex将保护此变量 10 int job_exclusive = 0; //只有一个线程能修改'job_exclusive',不需要保护 11 12 //此线程只能修改 'job_shared' 13 void job_1() 14 { 15 mutex.lock(); 16 std::this_thread::sleep_for(5 * interval); //令‘job_1’持锁等待 17 ++job_shared; 18 std::cout << "job_1 shared (" << job_shared << ")" << '\n'; 19 mutex.unlock(); 20 } 21 22 // 此线程能修改'job_shared'和'job_exclusive' 23 void job_2() 24 { 25 while (true) { //无限循环,直到获得锁并修改'job_shared' 26 if (mutex.try_lock()) { //尝试获得锁成功则修改'job_shared' 27 ++job_shared; 28 std::cout << "job_2 shared (" << job_shared << ")\n"; 29 mutex.unlock(); 30 return; 31 } else { //尝试获得锁失败,接着修改'job_exclusive' 32 ++job_exclusive; 33 std::cout << "job_2 exclusive (" << job_exclusive << ")\n"; 34 std::this_thread::sleep_for(interval); 35 } 36 } 37 } 38 39 int main() 40 { 41 std::thread thread_1(job_1); 42 std::thread thread_2(job_2); 43 44 thread_1.join(); 45 thread_2.join(); 46 47 return 0; 48 }
1 #include <chrono> 2 #include <mutex> 3 #include <thread> 4 #include <iostream> 5 6 std::chrono::milliseconds interval(100); 7 8 std::mutex mutex; 9 int job_shared = 0; //两个线程都能修改'job_shared',mutex将保护此变量 10 int job_exclusive = 0; //只有一个线程能修改'job_exclusive',不需要保护 11 12 void job_1() { 13 std::lock_guard<std::mutex> lockg(mutex); //获取RAII智能锁,离开作用域会自动析构解锁 14 std::this_thread::sleep_for(5 * interval); //令‘job_1’持锁等待 15 ++job_shared; 16 std::cout << "job_1 shared (" << job_shared << ")\n"; 17 } 18 19 void job_2() { 20 while (true) { //无限循环,直到获得锁并修改'job_shared' 21 std::unique_lock<std::mutex> ulock(mutex, std::try_to_lock); //以尝试锁策略创建智能锁 22 //尝试获得锁成功则修改'job_shared' 23 if (ulock) { 24 ++job_shared; 25 std::cout << "job_2 shared (" << job_shared << ")\n"; 26 return; 27 } else { //尝试获得锁失败,接着修改'job_exclusive' 28 ++job_exclusive; 29 std::cout << "job_2 exclusive (" << job_exclusive << ")\n"; 30 std::this_thread::sleep_for(interval); 31 } 32 } 33 } 34 35 int main() 36 { 37 std::thread thread_1(job_1); 38 std::thread thread_2(job_2); 39 40 thread_1.join(); 41 thread_2.join(); 42 43 return 0; 44 }
mutex在类内部的使用
考虑当mutex作为类内部成员的情况时,由于mutex是一个不可复制的变量,导致使用mutex的线程类不可复制,在外部构造线程时,需要用过std::ref传递引用。
示例代码:
1 class Point{ 2 private: 3 double x; 4 double y; 5 std::mutex m; 6 public: 7 Point(double _x, double _y){ 8 x = _x; 9 y = _y; 10 } 11 double calculateDistance(){ 12 m.lock(); 13 double result = pow((x*x + y*y), 0.5); 14 std::cout << result << std::endl; 15 m.unlock(); 16 return result; 17 } 18 void setCoordinate(double value){ 19 x = value; 20 y = value; 21 } 22 }; 23 24 int main(){ 25 26 const int loop = 100; 27 Point p(0, 0); 28 std::thread threads[loop]; 29 for(int i=0; i<loop;){ 30 p.setCoordinate(++i); 31 threads[i] = std::thread(&Point::calculateDistance, std::ref(p)); 32 } 33 for(int i=0; i<loop; i++){ 34 threads[i].join(); 35 } 36 return 0; 37 }
如果需要实现互锁的线程函数是一个const对象,需要对mutex添加std::mutable关键字,mutable修饰的成员可以在const函数中进行赋值操作
实际上,对于所有的const成员函数,如果有用于多线程的场合,都需要进行加锁处理
1 class Point{ 2 private: 3 double x; 4 double y; 5 mutable std::mutex m; 6 public: 7 Point(double _x, double _y){ 8 x = _x; 9 y = _y; 10 } 11 double calculateDistance() const{ 12 m.lock(); 13 double result = pow((x*x + y*y), 0.5); 14 std::cout << result << std::endl; 15 m.unlock(); 16 return result; 17 } 18 void setCoordinate(double value){ 19 x = value; 20 y = value; 21 } 22 }; 23 24 int RunMultiThread(){ 25 26 const int loop = 100; 27 Point p(0, 0); 28 std::thread threads[loop]; 29 for(int i=0; i<loop;){ 30 p.setCoordinate(++i); 31 threads[i] = std::thread(&Point::calculateDistance, std::ref(p)); 32 } 33 for(int i=0; i<loop; i++){ 34 threads[i].join(); 35 } 36 return 0; 37 }