具有共享/独占访问权限,且具有升级/降级功能的互斥锁
介绍
我的目标是创建可以充当读/写锁定机制的对象。任何线程都可以锁定它以进行读取,但是只有一个线程可以锁定它以进行写入。在写入线程释放它之前,所有其他线程都将等待。在释放任何其他线程之前,写线程不会获取互斥体。
我可以使用Slim Reader / Writer锁,但是:
- 它们不是递归的,例如,
AcquireSRWLockExclusive()
如果同一线程较早调用同一函数,则对的调用将阻塞。 - 它们不可升级,例如,已将锁锁定为读取访问权限的线程无法将其锁定为写入操作。
- 它们不是可复制的句柄。
我可以尝试C ++ 14,shared_lock
但是我仍然需要C ++ 11支持。此外,我还不确定它是否可以真正满足我的要求。
因此,我不得不手动实现它。由于缺少,删除了普通的C ++ 11方法WaitForMultipleObjects (nyi)
。现在具有升级/降级功能。
RWMUTEX
这一节很简单。
1 class RWMUTEX 2 { 3 private: 4 HANDLE hChangeMap; 5 std::mapThreads; 6 RWMUTEX(const RWMUTEX&) = delete; 7 RWMUTEX(RWMUTEX&&) = delete;
我需要std::map
为所有尝试访问共享资源的线程存储一个句柄,并且还需要一个互斥锁以确保对此映射的所有更改都是线程安全的。
构造函数
1 RWMUTEX(const RWMUTEX&) = delete; 2 void operator =(const RWMUTEX&) = delete; 3 4 RWMUTEX() 5 { 6 hChangeMapWrite = CreateMutex(0,0,0); 7 }
简单地创建一个映射互斥的句柄。对象不可复制。
创建
1 HANDLE CreateIf(bool KeepReaderLocked = false) 2 { 3 WaitForSingleObject(hChangeMap, INFINITE); 4 DWORD id = GetCurrentThreadId(); 5 if (Threads[id] == 0) 6 { 7 HANDLE e0 = CreateMutex(0, 0, 0); 8 Threads[id] = e0; 9 } 10 HANDLE e = Threads[id]; 11 if (!KeepReaderLocked) 12 ReleaseMutex(hChangeMap); 13 return e; 14 }
当调用LockRead()或LockWrite()来锁定对象时,将调用这个私有函数。如果当前线程还没有将自己变为可能访问这个互斥锁的线程中,这个函数将为该线程创建一个互斥锁。如果其他线程已经锁定这个互斥对象进行写访问,那么这个函数就会阻塞,直到写线程释放这个对象为止。这个函数返回当前线程的互斥句柄。
锁定读取/释放读取
1 HANDLE LockRead() 2 { 3 auto f = CreateIf(); 4 WaitForSingleObject(f,INFINITE); 5 return f; 6 } 7 void ReleaseRead(HANDLE f) 8 { 9 ReleaseMutex(f); 10 }
当您要锁定对象以进行读取访问并稍后释放它时,将调用这些函数。
锁/释放
1 void LockWrite() 2 { 3 CreateIf(true); 4 5 // Wait for all 6 vectorAllThreads; 7 AllThreads.reserve(Threads.size()); 8 for (auto& a : Threads) 9 { 10 AllThreads.push_back(a.second); 11 } 12 13 WaitForMultipleObjects((DWORD)AllThreads.size(), AllThreads.data(), TRUE, INFINITE); 14 15 // Reader is locked 16 } 17 18 void ReleaseWrite() 19 { 20 21 // Release All 22 for (auto& a : Threads) 23 ReleaseMutex(a.second); 24 ReleaseMutex(hChangeMap); 25 }
当您希望锁定对象以进行写访问并在稍后释放它时,将调用这些函数。函数的作用是:
1.在锁期间没有注册新线程
2.任何读取线程都释放了锁
析构函数
1 RWMUTEX() 2 { 3 CloseHandle(hChangeMap); 4 hChangeMap = 0; 5 for (auto& a : Threads) 6 CloseHandle(a.second); 7 Threads.clear(); 8 }
析构函数确保清除所有句柄。
可升级/可升级锁
有时,您希望将读锁升级为写锁,而不先解锁,以提高效率。因此,LockWrite
被修改为:
1 void LockWrite(DWORD updThread = 0) 2 { 3 CreateIf(true); 4 5 // Wait for all 6 AllThreads.reserve(Threads.size()); 7 AllThreads.clear(); 8 for (auto& a : Threads) 9 { 10 if (updThread == a.first) // except ourself if in upgrade operation 11 continue; 12 AllThreads.push_back(a.second); 13 } 14 auto tim = WaitForMultipleObjects((DWORD)AllThreads.size(), AllThreads.data(), TRUE, wi); 15 16 if (tim == WAIT_TIMEOUT && wi != INFINITE) 17 OutputDebugString(L"LockWrite debug timeout!"); 18 19 // We don't want to keep threads, the hChangeMap is enough 20 // We also release the handle to the upgraded thread, if any 21 for (auto& a : Threads) 22 ReleaseMutex(a.second); 23 24 // Reader is locked 25 } 26 27 void Upgrade() 28 { 29 LockWrite(GetCurrentThreadId()); 30 } 31 32 HANDLE Downgrade() 33 { 34 DWORD id = GetCurrentThreadId(); 35 auto z = Threads[id]; 36 auto tim = WaitForSingleObject(z, wi); 37 if (tim == WAIT_TIMEOUT && wi != INFINITE) 38 OutputDebugString(L"Downgrade debug timeout!"); 39 ReleaseMutex(hChangeMap); 40 return z; 41 }
调用Upgrade()现在的结果是:
更改被锁定的映射
等待除我们自己的线程之外的所有读线程退出
然后我们释放我们自己的线程互斥锁,因为更改锁定的映射就足够了。
调用Downgrade()
结果:
- 直接从映射上获取手柄,无需重新锁定
- 锁定此句柄,就像我们处于读取模式一样
- 发布变更映射
因此,整个代码是(带有一些调试帮助):
1 // RWMUTEX 2 class RWMUTEX 3 { 4 private: 5 HANDLE hChangeMap = 0; 6 std::mapThreads; 7 DWORD wi = INFINITE; 8 RWMUTEX(const RWMUTEX&) = delete; 9 RWMUTEX(RWMUTEX&&) = delete; 10 operator=(const RWMUTEX&) = delete; 11 12 public: 13 14 RWMUTEX(bool D = false) 15 { 16 if (D) 17 wi = 10000; 18 else 19 wi = INFINITE; 20 hChangeMap = CreateMutex(0, 0, 0); 21 } 22 23 ~RWMUTEX() 24 { 25 CloseHandle(hChangeMap); 26 hChangeMap = 0; 27 for (auto& a : Threads) 28 CloseHandle(a.second); 29 Threads.clear(); 30 } 31 32 HANDLE CreateIf(bool KeepReaderLocked = false) 33 { 34 auto tim = WaitForSingleObject(hChangeMap, INFINITE); 35 if (tim == WAIT_TIMEOUT && wi != INFINITE) 36 OutputDebugString(L"LockRead debug timeout!"); 37 DWORD id = GetCurrentThreadId(); 38 if (Threads[id] == 0) 39 { 40 HANDLE e0 = CreateMutex(0, 0, 0); 41 Threads[id] = e0; 42 } 43 HANDLE e = Threads[id]; 44 if (!KeepReaderLocked) 45 ReleaseMutex(hChangeMap); 46 return e; 47 } 48 49 HANDLE LockRead() 50 { 51 auto z = CreateIf(); 52 auto tim = WaitForSingleObject(z, wi); 53 if (tim == WAIT_TIMEOUT && wi != INFINITE) 54 OutputDebugString(L"LockRead debug timeout!"); 55 return z; 56 } 57 58 void LockWrite(DWORD updThread = 0) 59 { 60 CreateIf(true); 61 62 // Wait for all 63 AllThreads.reserve(Threads.size()); 64 AllThreads.clear(); 65 for (auto& a : Threads) 66 { 67 if (updThread == a.first) // except ourself if in upgrade operation 68 continue; 69 AllThreads.push_back(a.second); 70 } 71 auto tim = WaitForMultipleObjects((DWORD)AllThreads.size(), AllThreads.data(), TRUE, wi); 72 73 if (tim == WAIT_TIMEOUT && wi != INFINITE) 74 OutputDebugString(L"LockWrite debug timeout!"); 75 76 // We don't want to keep threads, the hChangeMap is enough 77 // We also release the handle to the upgraded thread, if any 78 for (auto& a : Threads) 79 ReleaseMutex(a.second); 80 81 // Reader is locked 82 } 83 84 void ReleaseWrite() 85 { 86 ReleaseMutex(hChangeMap); 87 } 88 89 void ReleaseRead(HANDLE f) 90 { 91 ReleaseMutex(f); 92 } 93 94 void Upgrade() 95 { 96 LockWrite(GetCurrentThreadId()); 97 } 98 99 HANDLE Downgrade() 100 { 101 DWORD id = GetCurrentThreadId(); 102 auto z = Threads[id]; 103 auto tim = WaitForSingleObject(z, wi); 104 if (tim == WAIT_TIMEOUT && wi != INFINITE) 105 OutputDebugString(L"Downgrade debug timeout!"); 106 ReleaseMutex(hChangeMap); 107 return z; 108 } 109 };
要使用RWMUTEX
,可以简单地创建锁定类:
1 class RWMUTEXLOCKREAD 2 { 3 private: 4 RWMUTEX* mm = 0; 5 public: 6 7 RWMUTEXLOCKREAD(const RWMUTEXLOCKREAD&) = delete; 8 void operator =(const RWMUTEXLOCKREAD&) = delete; 9 10 RWMUTEXLOCKREAD(RWMUTEX*m) 11 { 12 if (m) 13 { 14 mm = m; 15 mm->LockRead(); 16 } 17 } 18 ~RWMUTEXLOCKREAD() 19 { 20 if (mm) 21 { 22 mm->ReleaseRead(); 23 mm = 0; 24 } 25 } 26 }; 27 28 class RWMUTEXLOCKWRITE 29 { 30 private: 31 RWMUTEX* mm = 0; 32 public: 33 RWMUTEXLOCKWRITE(RWMUTEX*m) 34 { 35 if (m) 36 { 37 mm = m; 38 mm->LockWrite(); 39 } 40 } 41 ~RWMUTEXLOCKWRITE() 42 { 43 if (mm) 44 { 45 mm->ReleaseWrite(); 46 mm = 0; 47 } 48 } 49 };
还有一个用于升级机制的新类:
1 class RWMUTEXLOCKREADWRITE 2 { 3 private: 4 RWMUTEX* mm = 0; 5 HANDLE lm = 0; 6 bool U = false; 7 public: 8 9 RWMUTEXLOCKREADWRITE(const RWMUTEXLOCKREADWRITE&) = delete; 10 void operator =(const RWMUTEXLOCKREADWRITE&) = delete; 11 12 RWMUTEXLOCKREADWRITE(RWMUTEX*m) 13 { 14 if (m) 15 { 16 mm = m; 17 lm = mm->LockRead(); 18 } 19 } 20 21 void Upgrade() 22 { 23 if (mm && !U) 24 { 25 mm->Upgrade(); 26 lm = 0; 27 U = 1; 28 } 29 } 30 31 void Downgrade() 32 { 33 if (mm && U) 34 { 35 lm = mm->Downgrade(); 36 U = 0; 37 } 38 } 39 40 ~RWMUTEXLOCKREADWRITE() 41 { 42 if (mm) 43 { 44 if (U) 45 mm->ReleaseWrite(); 46 else 47 mm->ReleaseRead(lm); 48 lm = 0; 49 mm = 0; 50 } 51 } 52 };
用法示例:
1 RWMUTEX m; 2 3 // ... other code 4 void foo1() { 5 RWMUTEXLOCKREAD lock(&m); 6 } 7 8 void foo2() { 9 RWMUTEXLOCKWRITE lock(&m); 10 }