设计一个给任何对象加锁的 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 };
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 }
测试代码
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 }
总结:
1. 方便使用. 可以给任何对象加锁, 无论是自己设计的对象,还是标准库中的对象
2. 使用了标准库中的 std::recursive_mutex 和 std::lock_guard 两个类
3. 保护和访问都很明确. 仅当获得锁时,才能访问对象.
浙公网安备 33010602011771号