问题描述
我有以下代码,来自https://en.cppreference.com/w/cpp/thread/unique_lock。但是,在打印输出时,我看到了一些意外的结果,并且需要一些解释。
代码是:
#include <mutex>
#include <thread>
#include <chrono>
#include <iostream>
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from,Box &to,int anotherNumber)
{
// don't actually take the locks yet
std::unique_lock<std::mutex> lock1(from.m,std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m,std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1,lock2);
from.num_things += anotherNumber;
to.num_things += anotherNumber;
std::cout<<std::this_thread::get_id()<<" "<<from.num_things<<"\n";
std::cout<<std::this_thread::get_id()<<" "<<to.num_things<<"\n";
// 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
int main()
{
Box acc1(100); //initialized acc1.num_things = 100
Box acc2(50); //initialized acc2.num_things = 50
std::thread t1(transfer,std::ref(acc1),std::ref(acc2),10);
std::thread t2(transfer,5);
t1.join();
t2.join();
}
我的期望:
- acc1将使用num_things = 100初始化,而acc2将使用num_things = 50初始化。 假设线程t1首先运行,它获取具有2个锁的互斥锁m。一旦锁被锁定,并且可以将num_things分配给num = 10
- 完成后,将按顺序从from.num_things = 110和to.numthings = 60打印。 “从”开始,然后“到”。
- thread1完成了代码的关键部分,包装器unique_lock调用了它的析构函数,该析构函数基本上解锁了互斥体。
这是我不理解的。
我希望lock1填充首先被解锁,然后lock2被解锁。 然后线程t2以相同的顺序获取互斥对象,并先锁定lock1,然后再锁定lock2。它还将按顺序运行关键代码直到cout。
线程t2将从t1开始使用全局acc1.num_things = 110和acc2.num_things = 60。
我希望t2将首先从.num_things = 115开始打印,然后从to.numthings = 65开始打印。
但是,经过无数次试用,我总是得到相反的命令。那就是我的困惑。
解决方法
我希望lock1填充首先解锁,然后lock2解锁。
否,反之亦然。在您的函数中,首先构造lock1
,然后构造lock2
。因此,当函数返回时,lock2
首先被销毁,然后lock1
被销毁,因此lock2
的析构函数在lock1
的析构函数之前释放其锁定。
std::lock
设法获取多个锁的实际顺序与锁的销毁方式以及释放其各自互斥锁的所有权无关。这样做仍然遵循正常的C ++规则。
说线程t1首先运行,
您完全无法保证。在上面的代码中,t2
很有可能会首先进入该函数并获取互斥锁。而且,每次运行此程序也有可能获得不同的结果,t1
和t2
随机赢得比赛。
在不涉及技术专家的情况下,C ++唯一可以保证的是,std::thread
在线程函数在新的执行线程中被调用之前已被完全构造。您无法保证,当一个接一个地创建两个执行线程时,第一个执行线程将调用其函数并在第二个执行线程执行相同操作之前运行线程函数的任意部分。
因此,t2
很有可能偶尔会获得锁上的第一个Dib。或者,总是。试图控制执行线程之间事件的相对顺序比您想象的要困难得多。