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 }

 

posted @ 2020-03-05 16:02  Asp1rant  阅读(480)  评论(0编辑  收藏  举报