RWMutex:共享/专有的递归互斥锁

具有共享/独占访问权限,且具有升级/降级功能的互斥锁

 

介绍

我的目标是创建可以充当读/写锁定机制的对象。任何线程都可以锁定它以进行读取,但是只有一个线程可以锁定它以进行写入。在写入线程释放它之前,所有其他线程都将等待。在释放任何其他线程之前,写线程不会获取互斥体。

 

我可以使用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::map<DWORD, HANDLE> Threads;
6         RWMUTEX(const RWMUTEX&) = delete;
7         RWMUTEX(RWMUTEX&&) = delete;

 

我需要std::map<DWORD,HANDLE>为所有尝试访问共享资源的线程存储一个句柄,并且还需要一个互斥锁以确保对此映射的所有更改都是线程安全的。

构造函数

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                 vector<HANDLE> AllThreads;
 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::map<DWORD, HANDLE> Threads;
  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 }

 

posted @ 2019-10-12 14:59  梅塞尔-威廉  阅读(...)  评论(...编辑  收藏