当两个线程同时在SAME互斥体上尝试try_lock()时,会发生什么情况?

时间:2019-07-04 18:44:51

标签: c++ multithreading mutex undefined-behavior

这是我的第二篇文章。我的第一篇文章是从Intro到C类的本科课程。我希望这次能做得更好。

我正在为研究开发随机模型,并且正在寻找提高算法效率的方法。最近,一位同事向我介绍了多线程,并成功地将其实现到了我的一些不需要锁的算法中……

现在,我正在尝试修改/线程化需要锁定的算法。在我的文献/ StackOverflow / google搜索中,我已经学到了很多关于互斥锁类的知识。但是,我面临的问题似乎是独特的。对于上下文,我能找到的最接近答案的地方是:

What happens when two threads ATTEMPT to lock the same resource at the exact same time?

它仍然没有直接解决我要问的问题。

我一直在尝试使用动态分配的互斥对象指针数组来控制线程之间的内存访问。我的代码编译时没有警告/错误(使用w / flags -pthread -std = c ++ 11编译),并且可以完美执行,直到抛出Segfault的线程算法为止。在对lldb进行调查后,我发现抛出的异常如下:

lldb output screenshot

在进一步研究中,我发现lldb输出所引用的线程都在EXACT SAME互斥对象上尝试了try_lock(),因为包含该对象地址的指针数组已传递给每个线程。

我的问题与前面提到的帖子类似:

当同一互斥锁上的多个线程(不止一个)在完全相同的时间(处理器时钟的相同冲程)尝试try_lock()时,会发生什么情况?互斥类的较新实现是否针对此看似灾难性的事件(例如shard_mutex,timed_mutex等)提供解决方法?

最近这让我很震惊,因此任何见解将不胜感激。向S / O社区大声疾呼,以寻求所有帮助,这篇文章以及所有其他对我作为程序员的成长至关重要的帮助。

链接到代码:

https://github.com/tylerbalbright/StackOverflow_7_4.git

RVEdata.cpp中的第751行或第857行发生错误。

固定但不解决:

我能够使用互斥对象对象的双端队列来修复我的代码,而不是为动态创建的互斥对象创建指针矢量。该解决方案是由其他用户在这里提出的:

How can I use something like std::vector<std::mutex>?

在我的第一个(失败的)尝试中,我试图创建一个指向互斥量的指针数组,如下所示:

long int N = RuntimeVector.size(); //Varying size at runtime
std::mutex *MutexPtrs;
MutexPtrs = new std::mutex[N];

然后我将新创建的数组作为指针传递给函数,该函数会将指向数组的指针传递给新线程,如下所示:

void SomeFunction(std::mutex *PosLocks[])
{
.
..
...

    SearchersPos.push_back(std::async(std::launch::async, &RVEdata::PositiveSearch, this, PosLocks));
}

使用此方法,代码不会在每次执行时都失败,但是在使用EXC_BAD_ACCESS的情况下,失败率高达90%。但是,有趣的是,在每个执行失败的情况下,当多个线程尝试对同一互斥锁进行try_lock时,都会引发错误的访问。它绝不会失败,只有1个线程尝试对单个互斥锁进行try_lock。当我在HPC上运行相同的代码时,发生故障的可能性大约为95%,但是我在gdb中找不到的信息比在lldb中找到的信息还要多(我不太熟悉命令行gdb)。

PS-我在macOS High Sierra 10.13.6,Apple LLVM版本10.0.0(clang-1000.11.45.5)上运行。

2 个答案:

答案 0 :(得分:2)

当一个以上的线程在同一时间尝试锁定时,其中一个线程将最终锁定,而其他线程则会失败。

您的公主错误是由其他原因引起的。在另一个城堡

答案 1 :(得分:0)

  

当同一互斥锁上的多个线程(不止一个)在完全相同的时间(处理器时钟的相同冲程)尝试try_lock()时,会发生什么情况?互斥类的较新实现是否针对此看似灾难性的事件(例如shard_mutex,timed_mutex等)提供解决方法?

绝对没有什么灾难性的,这就是互斥对象。您尚未发现互斥体如何工作的缺陷,select并未损坏。您应该由StackOverflow的一位创始人阅读The First Rule of Programming

  

在我的第一个(失败的)尝试中,我试图创建一个指向互斥量的指针数组,如下所示:

问题是您要创建两个互斥锁数组

std::mutex *PosLocks;
std::mutex *GndLocks;

PosLocks = new std::mutex[N];
GndLocks = new std::mutex[N];

但是您要传递数组的地址

NetExists = FindNetwork(N, &PosLocks, &GndLocks);

调用此函数:

bool RVEdata::FindNetwork(long int N, std::mutex *PosLocks[], std::mutex *GndLocks[])

因此,现在您已经向函数传递了std::mutex**参数,该参数可以是指向互斥锁数组的指针指向互斥锁指针的数组。

然后您以{strong> mutex* 数组的形式访问它们,而不是指向mutex[]的指针

    if (PosLocks[i]->try_lock() == true)

PosLocks[i]索引到数组中以获得mutex*,然后使用->运算符取消引用指针。但这是倒退!您没有mutex*数组!

正如我在上面的评论中所说,您只是错误地访问了数组。这意味着程序尝试将mutex对象锁定在没有mutex对象的某个地址,因为您正在读取的地址位于互斥对象的中间,而不是在其开头

上面的行应首先解除对指针的引用,以到达mutex[],然后然后索引到数组:

    if ((*PosLocks)[i].try_lock() == true)

甚至更好的是首先停止将指针传递给数组,即将函数声明为:

bool RVEdata::FindNetwork(long int N, std::mutex PosLocks[], std::mutex GndLocks[])

并将其称为:

NetExists = FindNetwork(N, PosLocks, GndLocks);

现在,您可以直接访问数组:

    if (PosLocks[i].try_lock() == true)

更好的是,停止使用动态创建的数组:

std::vector<std::mutex> PosLocks;
std::vector<std::mutex> GndLocks;
// ...
NetExists = FindNetwork(N, PosLocks, GndLocks);
// ...

bool RVEdata::FindNetwork(long int N, std::vector<std::mutex>& PosLocks, std::vector<std::mutex>& GndLocks)
{
    // ...
    if (PosLocks[i].try_lock() == true)

如果不混合使用数组和指针以及动态分配,就不会发生此问题。