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

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

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

这是我写的一个快速测试,它是超级错误的,应该行不通,但确实如此:

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;
}

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

通常你会想要

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

(在 C++ 中,lock/unlock 通常通过使用 std::lock_guard 或类似的方法隐藏。)
假设一个线程(比如说线程 A)调用了这段代码并且在关键任务中,即它也持有锁。 那么如果你从另一个线程解锁同一个互斥量,那么除了A之外的任何线程也可以同时进入临界区。

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

就是说:您应该始终相信标准。仅当某些东西在某个系统上运行时,并不意味着它是可移植的。另外:特别是在并发环境中,很多事情可以解决一千次,但在竞争条件下第 1001 次失败。 在数学中,你的尝试相当于 "proof by example".

正如你所说,是UB。 UB 意味着它可以工作。或不。或者在工作和让您的计算机自己唱摇篮曲之间随机切换。 (另见 "nasal demons"。)

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

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

也就是说,由于您使用的平台上 C++ 互斥锁归结为 futex 实现的 "fast" pthread 互斥锁,这并非偶然。只是不能保证在任何时候或在实际检查您是否做正确的事情的任何情况下继续工作。