поэтому я хочу, чтобы программа выводила 1\n2\n1\n2\n1\n2\n, но, кажется, где-то застряла. Но когда я отлаживаю его и устанавливаю точку останова в cv1.notify_one() сразу после объявления t2, он выполняется??
#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;
}
Если вы хотите, чтобы два потока работали синхронно, вам не нужны потоки. Просто напишите две функции и вызывайте их поочередно.
Я думаю, что у вас есть гонка данных между запуском ваших потоков и вызовом cv1.notify_one();
в main()
.
Рассмотрим случай, когда вызов cv1.notify_one()
происходит до того, как поток 1 запустился и вызвал 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;
}
Также неясно, почему у вас есть два мьютекса, которые заблокированы за пределами основного. Я обычно думаю о мьютексе как о чем-то защищенном ресурсе, к которому нельзя обращаться одновременно. Похоже, идея заключалась в том, чтобы защитить вызовы cout, для которых вы должны использовать один мьютекс, чтобы каждый поток блокировался, выполнял cout, разблокировал и уведомлял другой.
В моем исходном ответе была точно такая же проблема между вызовами t1.notify() и t2.wait(). Если t1.notify() был вызван до ожидания потока 2, поток 2 никогда не просыпался.
Чтобы решить эту проблему, я добавил перечисление «Turn», которое указывает, чья сейчас очередь, и каждое условие ожидания теперь проверяет, их очередь или нет. Если это так, они не ждут, а просто распечатывают, поэтому, даже если уведомление было пропущено, они все равно выполнят свою задачу. Если это не их очередь, они будут блокироваться до тех пор, пока другой поток не установит переменную поворота и не вызовет уведомление.
ПРИМЕЧАНИЕ: Это демонстрирует хороший пример/практику, что обычно гораздо лучше иметь условие при использовании cv.wait(). Это одновременно проясняет намерения и позволяет избежать как потерянного пробуждения, так и ложного пробуждения.
ЗАМЕТКА 2 это решение может быть слишком сложным, и в целом условные переменные и мьютексы вряд ли будут лучшим решением этой проблемы.
После дальнейших размышлений я думаю, что вы можете избавиться от num_started_threads и использовать только одну condition_variable: 1. Добавление нового значения в Enum с именем «None», которое установлено в начале основного 2. перед cv1.notify() в main, установите значение enum равным T1. 3. замените все вызовы notify_one() на notify_all(). Но это, похоже, немного отличается от вашего исходного решения, поэтому не уверен, что вы хотели бы пойти по этому пути.
Другой ответ верен концептуально, но все еще имеет другое состояние гонки. Я запустил код, и он все равно заблокировался.
Проблема в том, что 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;
}
Есть несколько недостатков:
unique_lock
захватывают блокировки мьютексов в своих конструкторах. Таким образом, вы все время держите блокировки, и ни один поток не может двигаться вперед.unique_lock
захватывают блокировки мьютексов в своих конструкторах. Это делается в основном потоке. T1 и T2 разблокируют их через condition_variable
. Это поведение undefined (поток, которому принадлежит мьютекс, должен его разблокировать).mutex
condition_variable
в сочетании с мьютексом точки 2 и условием точки 1.Это гарантирует:
Это приводит к следующему коду (посмотреть вживую здесь):
#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;
}
отпустите свои замки, прежде чем уведомлять, может быть?