std::mutex 锁定的顺序
Posted
技术标签:
【中文标题】std::mutex 锁定的顺序【英文标题】:Order of std::mutex locking 【发布时间】:2014-01-11 01:06:31 【问题描述】:我很少考虑在两个连续的表达式之间,在调用函数和执行其主体的第一个表达式之间,或者在调用构造函数和执行其初始化程序之间会发生什么。然后我开始阅读有关并发的文章...
1.) 在对 std::thread
的构造函数的两次连续调用中,使用相同的可调用对象(例如函数、函子、lambda),其主体以 std::lock_guard
初始化并使用相同的 std::mutex
对象开始,执行标准保证第一个thread
构造函数调用对应的线程先执行锁保护代码?
2.) 如果标准没有做出保证,那么第二个thread
构造函数调用对应的线程是否有任何理论上或实际的可能性首先执行受保护的代码? (例如,在执行初始化程序或第一个 thread
构造函数调用的主体期间系统负载很重)
这是一个全局 std::mutex
对象 m
和一个全局 unsigned
num
初始化为 1
。在函数 foo
的主体的左大括号 和
std::lock_guard
之间只有空格。在main
中有两个std::thread
s t1
和t2
。 t1
首先调用线程构造函数。 t2
第二次调用线程构造函数。每个线程都由一个指向foo
的指针构成。 t1
使用unsigned
参数1
调用foo
。 t2
使用 unsigned
参数 2
调用 foo
。根据哪个线程首先锁定mutex
,num
的值将是4
或3
,在两个线程都执行了锁保护代码之后。如果t1
击败t2
锁定,num
将等于4
。否则,num
将等于 3
。我通过循环和在每个循环结束时将num
重置为1
进行了 100,000 次试验。 (据我所知,结果不也不应该取决于哪个线程首先是join()
ed。)
#include <thread>
#include <mutex>
#include <iostream>
std::mutex m;
unsigned short num = 1;
void foo(unsigned short par)
std::lock_guard<std::mutex> guard(m);
if (1 == num)
num += par;
else
num *= par;
int main()
unsigned count = 0;
for (unsigned i = 0; i < 100000; ++i)
std::thread t1(foo, 1);
std::thread t2(foo, 2);
t1.join();
t2.join();
if (4 == num)
++count;
num = 1;
std::cout << count << std::endl;
最后,count
等于100000
,所以t1
每次都赢得比赛。但这些试验并不能证明什么。
3.) 标准要求“首先调用thread
构造函数”是否总是意味着“首先调用传递给thread
构造函数的可调用对象”?
4.) 标准要求“首先调用传递给thread
构造函数的可调用对象”是否总是意味着“首先锁定mutex
”;假设在可调用的主体中,不存在依赖于在 std::lock_guard
初始化行之前传递给可调用的参数的代码? (同时排除任何 callable 的本地 static
变量,如被调用次数的计数器,可用于故意延迟某些调用。)
【问题讨论】:
不,是,不清楚,不。 【参考方案1】:-
不,标准不保证第一个线程首先获得锁。基本上,如果您需要在线程之间强加和排序,则需要在这些线程之间进行同步。即使第一个线程先调用互斥锁函数,第二个线程也可能先获取锁。
当然。例如,在产生线程时,您的应用程序可能只有一个内核可用,如果产生线程在产生第二个线程之后决定等待某事,则调度可能决定处理看到的最新线程,即第二个线程。即使有很多可用的内核,也有很多原因可以使第二个线程更快。
不,为什么会这样!第一步是产生一个线程并继续。当第一个函数对象被调用时,第二个线程可以运行并调用它的函数对象。
没有。线程之间没有顺序保证,除非您自己明确地强加它们,因为它们会破坏并发性的目的。
【讨论】:
以上是关于std::mutex 锁定的顺序的主要内容,如果未能解决你的问题,请参考以下文章