所以我希望程序输出1 \ n2 \ n1 \ n2 \ n1 \ n2 \ n,但是它似乎卡在了某个地方。但是,当我调试它并在声明t2之后立即在cv1.notify_one()处设置一个breackpoint时,它将执行??
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
cv1.wait(lck1);
cout << "1" << endl;
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
cv2.wait(lck2);
cout << "2" << endl;
cv1.notify_one();
}
});
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
答案 0 :(得分:2)
有几个缺陷:
unique_lock
会获取互斥锁的构造函数中的锁。因此,您一直都在持有锁,并且没有线程可以取得进展。unique_lock
获取了互斥锁在其构造函数中的锁。这是在主线程中完成的。 T1和T2正在通过condition_variable
对其进行解锁。这是未定义的行为(拥有互斥锁的线程必须将其解锁)。mutex
condition_variable
。这可以确保:
这将导致以下代码(see live here):
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
int main(int argc, char** argv)
{
condition_variable cv;
mutex mtx;
bool runt1 = true;
bool runt2 = false;
constexpr int COUNT = 3;
thread t1([&]()
{
for(int i = 0; i < COUNT; ++i)
{
unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [&](){ return runt1; });
cout << "1" << endl;
runt1 = false;
runt2 = true;
lck.unlock();
cv.notify_one();
}
});
thread t2([&]()
{
for(int i = 0; i < COUNT; ++i)
{
unique_lock<std::mutex> lck(mtx);
cv.wait(lck, [&](){ return runt2; });
cout << "2" << endl;
runt1 = true;
runt2 = false;
lck.unlock();
cv.notify_one();
}
});
t1.join();
t2.join();
return 0;
}
答案 1 :(得分:1)
我认为您在线程开始与cv1.notify_one();
中对main()
的调用之间存在数据争夺。
考虑在线程1启动并调用cv1.notify_one()
之前发生cv1.wait()
调用的情况。此后,没有人再呼叫cv1.notify
了,您的简历正在等待。这称为丢失唤醒。
您需要一种机制,可以在main中等待两个线程都启动,然后执行cv1.notify()
下面是一个使用int和互斥量的示例。
#include "pch.h"
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
using namespace std;
condition_variable cv1, cv2;
mutex m;
const int COUNT = 3;
enum Turn
{
T1,
T2
};
int main(int argc, char** argv)
{
mutex thread_start_mutex;
int num_started_threads = 0;
Turn turn = T1;
thread t1([&]() {
{
// increase the number of started threads
unique_lock<std::mutex> lck(thread_start_mutex);
++num_started_threads;
}
for (int i = 0; i < COUNT; ++i)
{
// locked cout, unlock before calling notify
{
unique_lock<std::mutex> lck1(m);
// wait till main thread calls notify
cv1.wait(lck1, [&] { return turn == T1;});
cout << "1 a really long string" << endl;
turn = T2; // next it's T2's turn
}
cv2.notify_one();
}
});
thread t2([&]() {
{
// increase the number of started threads
unique_lock<std::mutex> lck(thread_start_mutex);
++num_started_threads;
}
for (int i = 0; i < COUNT; ++i)
{
// locked cout, unlock before calling notify
{
unique_lock<std::mutex> lck2(m);
cv2.wait(lck2, [&] {return turn == T2;});
cout << "2 some other stuff to test" << endl;
turn = T1;
}
cv1.notify_one();
}
});
unique_lock<std::mutex> lck(thread_start_mutex);
// wait until both threads have started
cv1.wait(lck, [&] { return num_started_threads == 2; });
lck.unlock();
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
也不清楚为什么您有两个互斥锁被锁定在main之外。我通常认为互斥锁是一种受保护的资源,不应同时访问该资源。似乎这个想法是为了保护cout调用,为此您应该使用一个互斥锁,每个线程将锁定,执行cout,解锁并通知另一个线程。
我的原始答案在调用t1.notify()和t2.wait()之间存在完全相同的问题。 如果在线程2等待之前调用了t1.notify(),则线程2永远不会被唤醒。
为了解决这个问题,我添加了一个枚举“ Turn”,它指示轮到谁了,每个等待条件现在都检查是否轮到他们了。 如果是这样,他们就不会等待并且只是打印出来,因此即使错过了通知,他们仍然会执行任务。如果不是轮到他们,他们将阻塞,直到其他线程设置轮到变量并调用notify。
注意:这证明了一个很好的例子/做法,通常在使用cv.wait()时有条件会更好。这既使意图清晰,又避免了“丢失的唤醒”和“虚假的唤醒”。
注2 ,该解决方案可能过于复杂,一般情况下,变量和互斥体不太可能是解决此问题的最佳方法。
答案 2 :(得分:-1)
另一个答案在概念上是正确的,但仍然存在另一个种族条件。我运行了代码,它仍然会死锁。
问题是创建了t1
,但是直到执行cv1.wait(lck1)
之后,它才进入cv1.notify_one()
。因此,您的两个线程永远坐在一起等待。当您将断点放在该行上时,您可以演示这一点,以使线程赶上。同样,当一个线程完成但没有给另一时间调用wait()
时,此问题仍然存在,因此仅调用notify_one
。通过添加来自usleep(100)
的一些unistd.h
调用,可以看出这也是固定的*(松散使用)。
参见下文:
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
cv1.wait(lck1);
cout << "1\n";
usleep(100);
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
cv2.wait(lck2);
cout << "2\n";
usleep(100);
cv1.notify_one();
}
});
usleep(1000);
cv1.notify_one();
t1.join();
t2.join();
return 0;
}
编辑:要做得更好将是检查等待的线程,该线程未内置在您使用的互斥锁中。正确的方法可能是创建自己的互斥包装器类,并将该功能包括在类中,但是为了简单起见,我只做了一个waiting
变量。
参见下文:
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <unistd.h>
using namespace std;
mutex cout_lock;
condition_variable cv1, cv2, cv3;
mutex mtx1;
unique_lock<std::mutex> lck1(mtx1);
mutex mtx2;
unique_lock<std::mutex> lck2(mtx2);
int waiting = 0;
const int COUNT = 3;
int main(int argc, char** argv)
{
thread t1([&](){
for(int i = 0; i < COUNT; ++i)
{
waiting++;
cv1.wait(lck1);
cout << "1\n";
waiting--;
if(!waiting)
usleep(100);
cv2.notify_one();
}
});
thread t2([&](){
for(int i = 0; i < COUNT; ++i)
{
waiting++;
cv2.wait(lck2);
cout << "2\n";
waiting--;
if(!waiting)
usleep(100);
cv1.notify_one();
}
});
if(!waiting)
usleep(100);
cv1.notify_one();
t1.join();
t2.join();
return 0;
}