【问题标题】:Can SRW Lock be used as a binary semaphore?SRW Lock 可以用作二进制信号量吗?
【发布时间】:2020-10-18 05:37:39
【问题描述】:

Slim Reader/Writer (SRW) Locks 是 Windows 中的同步原语,从 Windows Vista 开始可用。

名称和接口表明它应该用作非定时共享非递归互斥锁。但是,通常也将其用作非共享互斥体,以避免 CRTICAL_SECTION 开销(仅使用独占 API)。

我注意到它也可以用作二进制信号量。这可以派上用场,因为 Windows API 中可用的其他信号量 - 事件对象和信号量对象 - 始终是内核调用,因此它可能是唯一可从 Windows API 轻松获得的轻量级信号量(并且 C++ 具有从 C++20 开始的信号量,并且boost 线程也不提供信号量)。

但这可靠吗?具体来说,我在文档中没有找到可以这样使用的明确信息。

但是,我没有发现任何禁止这种用法的东西。文档似乎不确定。

我期望的答案是什么:

  • 也许有人可以指出允许或禁止使用信号量的文档措辞
  • 也许有这种用法的一些实际经验
  • 也许直接参与 SRW 锁实现的人可以澄清(我认为有一些机会)

示例 - 这不会挂起

#include <Windows.h>
#include <atomic>


SRWLOCK lock = SRWLOCK_INIT;

std::atomic<bool> can_release{ false };

DWORD CALLBACK Thread1(LPVOID)
{
    for (int i = 0; i < 3; i++)
    {
        while (!can_release)
        {
            // spin
        }
        can_release = false;
        ::ReleaseSRWLockExclusive(&lock);
    }

    return 0;
}


DWORD CALLBACK Thread2(LPVOID)
{
    for (int i = 0; i < 3; i++)
    {
        can_release = true;
        ::AcquireSRWLockExclusive(&lock);
    }

    return 0;
}

int main() {
    ::AcquireSRWLockExclusive(&lock);

    HANDLE h1 = ::CreateThread(nullptr, 0, Thread1, nullptr, 0, nullptr);
    HANDLE h2 = ::CreateThread(nullptr, 0, Thread2, nullptr, 0, nullptr);

    ::WaitForSingleObject(h1, INFINITE);
    ::WaitForSingleObject(h2, INFINITE);

    ::CloseHandle(h1);
    ::CloseHandle(h2);
    
    return 0;
}

【问题讨论】:

  • 也可用作二进制信号量。 - 你的意思是什么?说代码示例
  • @RbMm,我添加了一个示例。大多数情况下,我的意思是AcquireSRWLockExclusiveReleaseSRWLockExclusive 可以从任意线程调用,只要没有与现有AcquireSRWLockExclusive 不匹配的ReleaseSRWLockExclusive
  • 首先你需要can_release = true;::AcquireSRWLockExclusive(&amp;lock); 之后而不是之前。如果从代码意义上讲,您尝试做什么?将 SRW 的所有权从一个线程转移到另一个线程?在某个线程中调用AcquireSRWLockExclusive(&amp;lock) 并匹配另一个线程中的AcquireSRWLockExclusive?是的,这可能,尽管不寻常
  • 只有获得的线程才能释放。违反此规则会调用未定义的行为。这是由应用程序验证者强制执行的。如果你只想要一个轻量级的信号量,你可以使用 WaitOnAddress。
  • 按照合约编写代码比编写实现代码要好得多。

标签: winapi semaphore readwritelock


【解决方案1】:

@Raymond Chen 是对的。应用程序验证程序报告相关代码的错误:

有问题的代码会产生这个错误:

=======================================
VERIFIER STOP 0000000000000255: pid 0x1A44: The SRW lock being released was not acquired by this thread. 

    00007FF73979C170 : SRW Lock
    00000000000025CC : Current ThreadId.
    00000000000043F4 : ThreadId of the thread that acquired the SRW lock.
    000001C1BEA8BF40 : Address of the acquire stack trace. Use dps <address> to see where the SRW lock was acquired.


=======================================
This verifier stop is continuable.
After debugging it use `go' to continue.

=======================================



=======================================
VERIFIER STOP 0000000000000253: pid 0x1A44: The SRW lock is being acquired recursively by the same thread. 

    00007FF73979C170 : SRW Lock
    000001C1BEA8BF40 : Address of the first acquire stack trace. Use dps <address> to see where the SRW lock was acquired.
    0000000000000000 : Not used
    0000000000000000 : Not used


=======================================
This verifier stop is continuable.
After debugging it use `go' to continue.

=======================================

截至目前,文档还明确禁止在不同的线程中发布,参见ReleaseSRWLockExclusiveReleaseSRWLockShared

SRW 锁必须由获得它的同一线程释放。您可以使用 Application Verifier 来帮助验证您的程序是否正确使用 SRW 锁(从 Basic 组启用 Locks checker)。

【讨论】:

  • 真的,验证器在这里显示错误并不意味着另一个线程不能释放锁。这可以从任何线程正式完成,直到获取/释放匹配并按顺序
  • AppVerifier 不同意。即使我只是在一个线程中获得锁并在另一个线程中释放它。它准确地说被释放的SRW锁不是由这个线程获取的。,所以它不想释放一个由不是这个线程获取的锁。这足以证明 SRW Lock 无意支持这种情况,即使它是意外实现或供内部使用
  • 那又怎样?这没有什么可以证明的。 AppVerifier 记住获取和释放线程的锁信息。它只是设计说这是错误的,因为这是非常不寻常的。但我非常了解 srw 如何在内部工作。因为我知道这是可能的——从另一个线程释放锁。在生产代码中-您不在 AppVerifier 下运行。另一种情况 - 我无论如何都没有看到你如何为你的任务使用 srw 锁
猜你喜欢
  • 2011-07-29
  • 2015-11-16
  • 2020-12-23
  • 2013-07-15
  • 1970-01-01
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多