c++ 为什么我不应该从不同的线程解锁互斥锁

c++ Why shouldn't I unlock a mutex from a different thread

本文关键字:解锁 线程 为什么 不应该 c++      更新时间:2023-10-16

为什么我不应该从不同的线程解锁互斥锁?在 c++ 标准中,它说得很清楚:如果互斥锁当前未被调用线程锁定,则会导致未定义的行为。但据我所知,一切都在Linux(Fedora 31与GCC(上按预期工作。我认真地尝试了一切,但我无法让它表现得很奇怪。 我所要求的只是一个例子,其中某些东西,实际上是任何东西都会受到解锁来自不同线程的互斥锁的影响。

这是我写的一个快速测试,它非常错误并且不起作用,但它确实有效:

std::mutex* testEvent;
int main()
{
testEvent = new std::mutex[1000];
for(uint32_t i = 0; i < 1000; ++i) testEvent[i].lock();
std::thread threads[2000];
auto lock = [](uint32_t index) ->void { testEvent[index].lock(); assert(!testEvent[index].try_lock()); };
auto unlock = [](uint32_t index) ->void { testEvent[index].unlock(); };
for(uint32_t j = 0; j < 1000; ++j)
{
for(uint32_t i = 0; i < 1000; ++i)
{
threads[i]      = std::thread(lock,i);
threads[i+1000] = std::thread(unlock,i);
}
for(uint32_t i = 0; i < 2000; ++i)
{
threads[i].join();
}
std::cout << j << std::endl;
}
delete[] testEvent;
}

正如你已经说过的,它是 UB。UB 意味着它可以工作。要不。或者在工作和让电脑唱摇篮曲之间随机切换。(另见"鼻魔"(

以下是人们可以在 Fedora 31 上使用 x86-64 上的 GCC 破坏您的程序的几种方法:

  1. 使用-fsanitize=thread编译。它现在每次都会崩溃,这仍然是一个有效的C++实现,因为 UB。
  2. 运行下赫尔格林德 (valgrind --tool=helgrind ./a.out(.它每次都会崩溃 - 仍然是托管C++程序的有效方式,因为UB。
  3. 目标系统上的 libstdc++/glibc/pthread 实现默认从使用"快速"互斥体切换到"错误检查"或"递归"互斥锁 (https://manpages.debian.org/jessie/glibc-doc/pthread_mutex_init.3.en.html(。请注意,这可能以与您的程序兼容的 ABI 方式实现,这意味着它甚至不必重新编译即可突然停止工作。

话虽如此,由于您使用的是C++互斥锁归结为futex实现的"快速"pthread互斥锁的平台,因此这不会偶然起作用。只是不能保证在任何时间或任何实际检查你是否在做正确事情的情况下继续工作。

>我真的想知道你为什么要这样做;)

通常你会想要类似的东西

lock();
do_critical_task();
unlock();

(在 C++ 中,锁定/解锁通常通过使用std::lock_guard或类似功能来隐藏。
让我们假设一个线程(假设线程 A(调用了此代码并且位于关键任务内,即它也持有锁。 然后,如果从另一个线程解锁相同的互斥锁,则 A 以外的任何线程也可以同时进入关键部分。

互斥锁的主要目的是相互排斥(因此得名(,所以你要做的就是消除互斥锁的目的;)

也就是说:你应该永远相信这个标准。只有当某些东西在某个系统上工作时,并不意味着它是可移植的。另外:特别是在并发上下文中,很多事情可以解决一千次,但随后作为竞争条件的第 1001 次失败。 在数学中,你的尝试可以与"例子证明"相媲美。