首页 > 解决方案 > c++ 为什么我不应该从不同的线程解锁互斥锁

问题描述

为什么我不应该从不同的线程解锁互斥锁?在 c++ 标准中,它说得很清楚:如果互斥锁当前没有被调用线程锁定,它会导致未定义的行为。但据我所知,在 Linux(Fedora 31 with 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;
}

标签: c++linuxmutexundefined-behavior

解决方案


正如你已经说过的,它是UB。UB 表示它可以工作。或不。或者在工作和让你的电脑自己唱摇篮曲之间随机切换。(另见“鼻恶魔”。)

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

  1. 用 编译-fsanitize=thread。它现在每次都会崩溃,这仍然是一个有效的 C++ 实现,因为 UB.
  2. 在 hlgrind ( 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 互斥锁,因此这不会意外地起作用。只是不能保证在任何时候或在任何实际检查您是否在做正确的事情的情况下都可以继续工作。


推荐阅读