所以我希望程序输出1 \ n2 \ n1 \ n2 \ n1 \ n2 \ n但它似乎卡在某处。但是当我调试它并在声明t2之后立即在cv1.notify_one()处设置断点时它会执行??
#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;
}
有几个缺点:
unique_lock
s在他们的构造函数中获取互斥锁的锁。所以你一直持有锁,没有线程可以取得进展。
你的全球unique_lock
s在他们的构造函数中获取互斥锁的锁。这是在主线程中完成的。 T1和T2正在通过condition_variable
解锁它们。这是未定义的行为(拥有互斥锁的线程必须将其解锁)。mutex
保护这个变量condition_variable
结合第2点的互斥锁和第1点的条件。这可以确保:
这导致以下代码(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;
}
我认为你的线程开始和cv1.notify_one();
中main()
的调用之间存在数据竞争。
考虑cv1.notify_one()
调用在线程1启动之前发生并调用cv1.wait()
的情况。之后没有人再打电话给cv1.notify
而且你的cv-s只是在等待。这称为失落唤醒。
你需要一个机制在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:这个解决方案可能过于复杂,一般来说条件变量和互斥量不太可能是解决这个问题的最佳方法。
另一个答案在概念上是正确的,但仍然有另一种竞争条件。我运行代码,它仍然会死锁。
问题是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;
}