我正在为考试做准备,遇到了这个问题,询问以下代码的潜在输出。我感到困惑,为什么使用.lock()时,另一个正在运行的线程仍然可以与“锁定”线程。
void print(char c); // output character c
std::mutex m; // global variable useable by all threads
void A(){
m.lock();
print(’A’);
print(’B’);
m.unlock();
}
void B(){
print(’C’);
std::thread t(A);
print(’D’);
t.join();
}
int main(){
std::thread t(B);
t.join();
}
我认为,由于使用了锁,预期的输出应仅为“ CABD”,但答案是“ CABD”,“ CDAB”和“ CADB”。请您解释一下,谢谢。
答案 0 :(得分:4)
我很困惑,为什么使用.lock()时,另一个正在运行的线程仍然可以与“锁定”线程竞争资源。
的确,您很困惑。没有“锁定线程”之类的东西,或者至少意味着其他东西。锁是一个同步对象。当两个线程尝试在同一对象上 m.lock();
上调用std::mutex m;
时,其中一个将获得并获得锁,而另一个将等待直到锁被释放。因此,m.lock();
是同步点,并且所有线程必须命中相同的同步点,才能进行任何锁定。
因此,在您的代码中,只有void A()
函数会获取并释放锁。因此,要实现同步,必须从多个线程中调用此函数。但是,您创建了两个线程:main
中的一个调用void B()
,另一个线程void B()
中的一个调用void A()
。因此,我们总共有3个线程(包括主线程),但是其中只有一个调用void A()
。因此根本没有同步。
因此,在C
必须在A B D
之后发生的约束下,将发生的第一件事是(打印)B
然后A
以任何顺序进行(因为顺便说一句,我强烈建议使用void A()
的命名方式,以免混淆。因此,可能的结果就是您所描述的。
这当然是基于void print(char c);
是线程安全的假设,因为如果不是print
,那么注定会发生任何事情:段错误(如果幸运的话),系统崩溃,正确的结果,错误的结果,一个黑洞吞噬着大地,我失去了重量,任何东西。在这种情况下,您甚至无法在没有同步的情况下呼叫print
。
旁注::您可能想使用std::lock_guard来代替手动锁定和解锁。目前,您的代码还不是异常安全的,除非const displayCartTotal= ({results})=>{
const {0: data} = results;
const {itemsInCart, buyerCountry} = data;
}
不抛出异常。无论如何,手动锁定(解锁)是一个坏习惯。
答案 1 :(得分:2)
您误解了互斥体的工作原理。互斥锁仅保护其lock()
和unlock()
中的代码区域,因此它仅保护A()
。在多个线程中多次调用A()
会显示此信息。
让我们看看您的代码在起作用:
Main thread: | Thread t(B):
// create a new thread and run `B()` on it.|
std::thread t(B); |
/* wait for the new thread to finish */ | //Come alive.
t.join(); | //print 'C'.
| print('C');
| //start a new thread t(A)
| std::thread t(A);
| //print 'D'
| print('D');
| t.join(); //wait for thread t(B) to finish.
现在我们的输出看起来像C
,因为t(B)
刚刚打印了C
,在此之前什么也没打印。让我们展示t(A)
将会做什么:
Thread t(A):
//lock our global mutex `m`.
m.lock();
// print 'A'
print('A');
// print 'B'
print('B');
//unlock our global mutex `m`.
m.unlock();
由于没有人持有当前的互斥锁m
,我们可以立即将其拿走,锁定它并继续执行我们的代码。
输出的差异是由于std::thread
并不总是在您每次使用它创建对象时都立即运行。当涉及到线程调度时,有很多因素在起作用,这是另一个答案。当看到输出时,这确实给了我们摆动的空间。在t(B)
的线程产生的那一刻,让t(A)
和t(A)
一起匹配:
Thread t(B): | Thread t(A):
/*start a new thread t(A)*/ |
std::thread t(A); | //come alive.
从现在开始,可能会发生以下事情:
t(A)
会生成并启动,但t(B)
更快,并且在D
有机会打印t(A)
或A
之前打印B
。或
t(A)
,它比t(B)
快,并打印出A
和B
,然后t(B)
打印出{{1} }。或
D
,它比t(A)
快并打印出t(B)
,但随后A
赶上并打印t(B)
,然后{ {1}}可以打印出D
。这意味着您看到的组合都是有效的。在线程t(B)
甚至可以开始打印之前,您的互斥体无法阻止线程B
打印t(B)
。
要使用互斥锁并在运行中查看它,请尝试生成2个调用D
的线程:
t(A)
现在,其中一个线程必须在另一个线程上等待互斥体才能解锁,然后才能进入锁定区域。
这意味着这两个线程都可以如下运行:
A
这也可能以其他方式发生,t2在t之前获取互斥量,从而在t2之前打印。
由于互斥锁,您总是会看到print(’C’);
std::thread t(A);
std::thread t2(A);
print(’D’);
t.join();
t2.join();
的结果,并且永远不会看到t(A): | t2(A):
|
|
/*takes lock.*/ |
m.lock(); | /*tries to take lock but fails because t(A) already holds it and thus has to wait*/
| m.lock();
print('A'); | //..still waiting..
print(’B’); | // zzz..
m.unlock(); | //now that the mutex is free we can lock() and print!
//done | // print A & B and unlock mutex..
或ABAB
,因为两个线程不能同时位于关键互斥锁部分中。