在共享模式下锁定的 std::shared_mutex 上调用解锁

Call unlock on std::shared_mutex that is locked in shared mode

C++17 引入了 std::shared_mutex type. I have been looking at the documentation over on CppReference,特别关注产生未定义行为的情况。

在通读两种解锁方法(一种用于释放独占所有权,一种用于释放共享所有权)时,我注意到文档有一次有点含糊。

对于 std::shared_mutex::unlock_shared,文档说明(强调我的):

The mutex must be locked by the current thread of execution in shared mode, otherwise, the behavior is undefined.

明确指出调用 unlock_shared 之前必须调用 lock_shared,因为这是在共享模式下锁定互斥锁的唯一方法。

对于 std::shared_mutex::unlock,文档指出:

The mutex must be locked by the current thread of execution, otherwise, the behavior is undefined.

没有提及当前执行线程在调用 unlock 之前必须持有的访问级别。这让我想知道它是否也能够释放共享所有权和独占所有权。

我的问题: 通过调用 unlock 而不是 unlock_shared 来释放 std::shared_mutex 的共享所有权是未定义的行为吗?

如果可能的话,我想要引用 C++ 标准中明确确认或否认上述场景中未定义行为的内容。

根据 [thread.mutex.requirements.mutex] 我们有

The expression m.unlock() shall be well-formed and have the following semantics:

Requires: The calling thread shall own the mutex.

Effects: Releases the calling thread's ownership of the mutex.

Return type: void.

Synchronization: This operation synchronizes with subsequent lock operations that obtain ownership on the same object.

Throws: Nothing.

因此,只要线程拥有互斥锁,无论是否处于共享模式,unlock都会释放线程对互斥锁的所有权。

我没有通过标准来验证它到底说了什么,但是the original proposal明确指出lock/unlocklock_shared/unlock_shared 通话应配对:

shared_mutex  | Semantics
--------------+---------------------------------------------
lock          | Lock the mutex in unique ownership mode
unlock        | unlock the mutex from unique ownership mode
lock_shared   | Lock the mutex in shared ownership mode
unlock_shared | unlock the mutex from shared ownership mode

它还明确指出,由于 SRWLOCK 在 Windows 上的实现方式,因此存在这种分离:

Additionally, some operating systems (e.g. Windows) have different names for unlocking shared vs unlocking unique as well. Use of different names in the C++ API allows for a more efficient binding to such OS API's.

如果标准没有在某处明确提及这一点,则很可能是标准中的缺陷。不管这是故意的,实际上所有 Windows 上 MSVC 附带的 shared_mutex 实现都不会像那样工作,因为它们 需要 配对 lock-unlock 调用正确。

在 POSIX 系统上,unlockunlock_shared 确实映射到一个函数 pthread_rwlock_unlock,因此两者很可能在那里是等价的。这是不应依赖的副作用。


On Windows 7 SRWLOCK 使用最低有效位将锁标记为已被 reader 或 writer 锁定。函数 unlock 清除该位,破坏锁,而 unlock_shared 仅在最后一个 reader 离开锁时才清除它。以下示例显示锁如何更改其状态:

mtx.lock_shared(); // 0x11 - 1 reader, locked (shared)
mtx.lock_shared(); // 0x21 - 2 readers, locked (shared)
mtx.unlock();      // 0x20 - 2 readers, unlocked (invalid state)
mtx.lock();        // 0x21 - 2 readers, locked (shared)

即使有另一个 reader 持有锁,最终 lock 也能够获取互斥锁。之后std::shared_mutex被认为被锁定了2readers,所以更多的readers可以进入锁。因此,在上面的序列中,lock 已将互斥体锁定为共享所有权模式,而不是误用的副作用。