yun@dicom

导航

设计一个给任何对象加锁的 C++ 模板, 用于多线程互斥访问

设计一个给任何对象加锁的 C++ 模板, 用于多线程互斥访问

 

前言:

众所周知,如果多个线程同时访问一个共享的对象,就需要给这个对象加锁.
否则,很可能发生时序方面的逻辑错误,更严重的会导致访问例外.
关于这方面的文章已经非常多了.不多说了.

无论是C++11标准库,还是boost,都没有提供支持并发(或者说线程安全)的容器.

本文旨在用C++11中提供的并发控制类,来设计一个可以给任何对象加锁的 C++ 模板.
有了这个模板,我们可以方便支持多线程访问任何一个共享数据,无论是自己设计的类实例,还是C++11标准库中的通用容器.

 

设计要求和思路

以下是基本的设计要求和思路:

1. 用 C++ 的 template, 这样可以方便地封装任何对象
2. 我们需要能封装任何对象, 因此不能针对特定对象进行继承或改造.
3. 需要设计一个 Lock 函数, 调用后, 返回封装对象的指针或引用
4. 除了 Lock 函数外, 没有任何途径能让调用者看见封装对象的指针或引用
    换言之, 如果调用者忘记调用 Lock 函数, 将无法访问这个被封装的对象.
5. 锁也是一个对象.
    当函数离开时,锁对象自动析构,程序员可以不用显式解锁.
    这样就避免了因为忘记解锁导致死锁.

 

基本框架


按照上述思路, 可以给出模板的简单框架如下:

 1 // ExclusiveHolder 是一个互斥访问容器
 2 template <typename T> class ExclusiveHolder
 3 {
 4 protected:
 5     T * m_pObject;
 6 
 7 public:
 8     ExclusiveHolder ();
 9     explicit ExclusiveHolder (T * object);
10     virtual ~ExclusiveHolder ();
11 
12 public:
13     void Attach (T * object);    //    把要封装的对象放入互斥访问容器中
14 
15    // 设计一个锁对象, 以互斥访问容器作为构造函数的参数
16 public:
17     class LockHolder
18     {
19     public:
20         LockHolder (const ExclusiveHolder * holder);
21         ~LockHolder ();    //    在析构函数中解锁
22 
23     public:
24         T * As ();    //    把锁转换成封装对象的指针
25     };
26 
27 public:
28     inline LockHolder Lock ()
29     {
30         return LockHolder (this);
31     }
32 };

 

我们以C++11标准库中的 std::list 作为例子, 简单的测试代码如下:

 

 1 //    一个互斥队列, 可供多个线程并发访问
 2 static ExclusiveHolder <std::list <int> > arQueue (new std::list <int> ());
 3 
 4 
 5 //    线程1, 往队列中添加
 6 static void THPush ()
 7 {
 8     ...
 9     {  // (1)
10         auto lock = arQueue.Lock ();        //    获得锁
11         std::list <int> * lst = lock.As ();    //    把锁转成对象指针
12         lst->push_front ();              //    访问对象
13     }
14     ...
15 }
16 
17 
18 //    线程2, 从队列中获取或删除
19 static void THPop ()
20 {
21     ...
22     {  // (2)
23         auto lock = arQueue.Lock ();        //    获得锁
24         std::list <int> * lst = lock.As ();    //    把锁转成对象指针
25         if (!lst->empty ())             //    访问对象
26             lst->pop_back ();
27     }
28     ...
29 }
30 
31 void Test_ExclusiveList ()
32 {
33     std::thread t1 (THPush);
34     std::thread t2 (THPop);
35 
36     t1.join ();
37     t2.join ();
38 }

 

这里要特别注意上面第9行和第22行的标记为  (1) 和 (2) 的位置, 改成程序块的目的有几个:

1. 为了尽快释放锁 - 在程序块结束的地方 (而不是函数退出的地方), 锁就被自动释放了;

2. 在程序块中,  lock.As () 返回的对象指针, 其作用范围是在这个程序块中, 离开程序块后, 这个指针就无效了,恰好也满足了锁释放后,不得再访问指针的原则

3. 在整个函数中, 程序块可以明显标识出锁的范围

 

完整的代码和测试代码

完整的代码和测试代码如下

 

ExclusiveHolder.tlh

 

  1 #pragma once
  2 
  3 #include <mutex>
  4 
  5 
  6 //-----------------------------------------------------------------------------
  7 //        ExclusiveHolder.tlh
  8 //    允许互斥访问一个指针对象
  9 //-----------------------------------------------------------------------------
 10 
 11 template <typename T> class ExclusiveHolder
 12 {
 13 protected:
 14     T * m_pObject;
 15 
 16 public:
 17     ExclusiveHolder ();
 18     explicit ExclusiveHolder (T * object);
 19 
 20     virtual ~ExclusiveHolder ();
 21 
 22 private:
 23 //    禁止拷贝构造函数
 24     ExclusiveHolder (const ExclusiveHolder & h) = delete;
 25 //    禁止复制
 26     ExclusiveHolder & operator = (const ExclusiveHolder & h) = delete;
 27 
 28 public:
 29     void Attach (T * object);
 30     T * Detach ();
 31 
 32     ExclusiveHolder & operator = (T * object);
 33 
 34     bool IsEmpty () const;
 35 
 36     void Release ();
 37 
 38 protected:
 39     mutable std::recursive_mutex m_Mutex;
 40 
 41 public:
 42     class LockHolder
 43     {
 44     public:
 45         LockHolder (const ExclusiveHolder * holder);
 46         ~LockHolder ();
 47 
 48         LockHolder (LockHolder && from);
 49 
 50     private:
 51 //    可以对同一个 ExclusiveHolder 对象反复调用 Lock, 但是 LockHolder 还是不要拷贝构造了吧
 52         //    禁止拷贝构造函数
 53         LockHolder (const LockHolder & h) = delete;
 54         //    禁止复制
 55         LockHolder & operator = (const LockHolder & h) = delete;
 56 
 57     public:
 58         T * As ();
 59         const T * As () const;
 60 
 61         template <typename C> C * AS ();
 62         template <typename C> const C * AS () const;
 63 
 64         T * operator -> ()
 65         {
 66             return As ();
 67         }
 68         const T * operator -> ()  const
 69         {
 70             return As ();
 71         }
 72 
 73     protected:
 74         std::unique_ptr <std::lock_guard < std::recursive_mutex > > m_Lock;
 75         const ExclusiveHolder * m_Holder;
 76     };
 77 
 78 public:
 79     inline LockHolder Lock ()
 80     {
 81         return LockHolder (this);
 82     }
 83 
 84     inline LockHolder Lock () const
 85     {
 86         return LockHolder (this);
 87     }
 88 
 89     template <class Action> void LockExec (Action action)
 90     {
 91         LockHolder holder (this);
 92         action (holder.As ());
 93     }
 94 
 95     template <class Action> void LockExec (Action action) const
 96     {
 97         LockHolder holder (this);
 98         action (holder.As ());
 99     }
100 };
View Code

 

ExclusiveHolder.tli

 

  1 // ExclusiveHolder.tli
  2 //
  3 
  4 #pragma once
  5 
  6 
  7 #include "ExclusiveHolder.tlh"
  8 
  9 
 10 template <typename T>
 11 ExclusiveHolder <T>::ExclusiveHolder ()
 12 {
 13     m_pObject = NULL;
 14 }
 15 
 16 
 17 template <typename T>
 18 ExclusiveHolder <T>::~ExclusiveHolder ()
 19 {
 20     LockHolder Lock (this);
 21     delete m_pObject;
 22 }
 23 
 24 template <typename T>
 25 ExclusiveHolder <T>::ExclusiveHolder (T * object)
 26 {
 27     m_pObject = object;
 28 }
 29 
 30 
 31 template <typename T>
 32 void ExclusiveHolder <T>::Attach (T * object)
 33 {
 34     LockHolder Lock (this);
 35 
 36     delete m_pObject;
 37     m_pObject = object;
 38 }
 39 
 40 template <typename T>
 41 T * ExclusiveHolder <T>::Detach ()
 42 {
 43     LockHolder Lock (this);
 44 
 45     T * old = m_pObject;
 46     m_pObject = NULL;
 47     Release ();
 48 
 49     return old;
 50 }
 51 
 52 
 53 template <typename T>
 54 void ExclusiveHolder <T>::Release ()
 55 {
 56     LockHolder Lock (this);
 57 
 58     delete m_pObject;
 59     m_pObject = NULL;
 60 }
 61 
 62 
 63 template <typename T>
 64 ExclusiveHolder <T> & ExclusiveHolder <T>::operator = (T * object)
 65 {
 66     Attach (object);
 67     return *this;
 68 }
 69 
 70 
 71 template <typename T>
 72 bool ExclusiveHolder <T>::IsEmpty () const
 73 {
 74     LockHolder Lock (this);
 75 
 76     return (m_pObject == NULL);
 77 }
 78 
 79 
 80 template <typename T>
 81 ExclusiveHolder <T>::LockHolder::LockHolder (const ExclusiveHolder <T> *holder)
 82 {
 83     auto lock = new std::lock_guard < std::recursive_mutex > (holder->m_Mutex);
 84     m_Lock.reset (lock);
 85     m_Holder = holder;
 86 }
 87 
 88 template <typename T>
 89 ExclusiveHolder <T>::LockHolder::LockHolder (LockHolder && from)
 90 {
 91     m_Lock.swap (from.m_Lock);
 92     m_Holder = from.m_Holder;
 93 }
 94 
 95 
 96 template <typename T>
 97 ExclusiveHolder <T>::LockHolder::~LockHolder ()
 98 {
 99 }
100 
101 
102 template <typename T>
103 T * ExclusiveHolder <T>::LockHolder::As ()
104 {
105     return m_Holder->m_pObject;
106 }
107 
108 
109 template <typename T>
110 const T * ExclusiveHolder <T>::LockHolder::As () const
111 {
112     return m_Holder->m_pObject;
113 }
114 
115 template <typename T>
116 template <typename C> C * ExclusiveHolder <T>::LockHolder::AS ()
117 {
118     return m_Holder->m_pObject;
119 }
120 
121 template <typename T>
122 template <typename C> const C * ExclusiveHolder <T>::LockHolder::AS () const
123 {
124     return m_Holder->m_pObject;
125 }
View Code

 

测试代码

 

 1 #include <thread>
 2 #include <chrono>
 3 #include <random>
 4 using std::default_random_engine;
 5 using std::uniform_int_distribution;
 6 
 7 #include "ExclusiveHolder.tlh"
 8 #include "ExclusiveHolder.tli"
 9 
10 
11 //-----------------------------------------------------------------------------
12 //    用来测试 ExclusiveHolder 的单元测试
13 //-----------------------------------------------------------------------------
14 
15 static ExclusiveHolder <std::list <int> > arQueue (new std::list <int> ());
16 
17 
18 static void THPushPop ()
19 {
20     static default_random_engine e;
21     static uniform_int_distribution <unsigned> u (10, 30);    // 随机数分布对象
22 
23     for (int Index = 0; Index < 1000; Index++)
24     {
25         int rnd = u (e);
26 
27         //    TRACE ("\r\nrnd : %d", rnd);
28 
29         auto lock = arQueue.Lock ();
30         auto lst = lock.As ();
31 
32         if (rnd & 1)
33             lst->push_front (Index);
34         else
35             if (!lst->empty ())
36                 lst->pop_front ();
37 
38         std::this_thread::sleep_for (std::chrono::milliseconds (rnd));
39     }
40 }
41 
42 
43 
44 // 创建 8 个线程, 线程中随机访问 list
45 void Test_ExclusiveList ()
46 {
47     std::thread t1 (THPushPop);
48     std::thread t2 (THPushPop);
49     std::thread t3 (THPushPop);
50     std::thread t4 (THPushPop);
51 
52     std::thread t5 (THPushPop);
53     std::thread t6 (THPushPop);
54     std::thread t7 (THPushPop);
55     std::thread t8 (THPushPop);
56 
57     t1.join ();
58     t2.join ();
59     t3.join ();
60     t4.join ();
61     t5.join ();
62     t6.join ();
63     t7.join ();
64     t8.join ();
65 }
View Code

 

总结:

1. 方便使用. 可以给任何对象加锁, 无论是自己设计的对象,还是标准库中的对象

2. 使用了标准库中的 std::recursive_mutex 和 std::lock_guard 两个类

3. 保护和访问都很明确. 仅当获得锁时,才能访问对象.

posted on 2020-09-30 15:02  yun@dicom  阅读(741)  评论(0)    收藏  举报