由于其他人的问题,我再次提出了一个问题——这次是这个问题。
我想尝试并行编程并通过条件变量解决问题(一个线程将字符串添加到列表中,另一个从那里获取它们,对它们进行排序并将它们写入自己的线程)。它似乎有效,但有时会关闭
......
<-- String consumed
<-- Wait string produced
--> String produced
--> Wait string consumed
值得。我哪里傻了?如果我尝试向条件变量添加条件,它只会变得更糟。
这是我的代码:
#include <list>
#include <string>
#include <iostream>
#include <iomanip>
#include <thread>
#include <condition_variable>
#include <algorithm>
#include <ctime>
using namespace std;
constexpr int ELEMENTS = 10;
void printList(const list<string>& l) {
cout <<"LIST:\n";
for(const auto& s: l) cout << s << " ";
cout << endl;
}
string new_string() {
string s;
for(int i = rand()%8+1; i > 0; --i)
s += rand()%26+'a';
return s;
}
// Сигналы о том, что строка готова и что обработана
condition_variable strReady, strHandled;
mutex mReady, mHandled;
void createList(list<string>& l) {
{
unique_lock lck(mHandled); // Ждем запуска второго потока
strHandled.wait(lck); // Без проверок, так как заведомо знаем,
} // что он один
for(int i = 0; i < ELEMENTS; ++i) {
// Начинает создавать
string s = new_string();
l.push_back(s);
cout << "--> String produced" << endl;
strReady.notify_one(); // Уведомляем о готовности строки
cout << "--> Wait string consumed" << endl;
unique_lock lck(mHandled); // и ждем разрешения работать
strHandled.wait(lck); // Без проверок, так как заведомо знаем,
} // что поток обработчика единственный
}
void handleList(list<string>& l1, list<string>& l2) {
strHandled.notify_one(); // Сообщаем о запуске, можно работать
for(int i = 0; i < ELEMENTS; ++i) {
string s;
// Ждет сигнала
{
unique_lock lck(mReady);
strReady.wait(lck); // Без проверок, так как заведомо знаем,
// что поток создателя единственный
s = l1.back();
cout << "<-- String consumed" << endl;
}
strHandled.notify_one(); // Строка скопирована, сообщаем, что
// можно работать дальше
sort(s.begin(),s.end());
l2.push_back(s);
cout << "<-- Wait string produced" << endl;
}
}
int main() {
srand(time(0));
list<string> l1, l2;
thread t1(createList,ref(l1));
thread t2(handleList,ref(l1),ref(l2));
t1.join();
t2.join();
printList(l1);
printList(l2);
}
仍然 neponyatka - is srand(time(0))
,但线条总是相同的。
您的代码有几个问题,难度不同。首先,一些无用的开场白:
和
strHandled.notify_one();
- 删除。不需要这些序言。第二:需要检查,因为 有一个虚假的唤醒。那些。线程可以唤醒不是因为它收到了信号,而仅仅是因为。所以总是需要额外的检查。
第三,你的代码中有一个竞赛,这意味着 UB。在第一个线程中,您
l.push_back(s);
没有保护互斥锁,这会与第二个线程的这条线产生竞争:s = l1.back();
. 关闭使用互斥锁将字符串添加到列表mReady
并离开。一般来说,因为 您正在尝试完全序列化 2 个线程,即 为了使它们顺序可执行,一个互斥体对你来说就足够了——你不需要两个,只有一个资源。
第四,也是最重要的,如果它
strReady.notify_one();
被执行并立即切换到另一个线程,即wait
没有时间处理,那么第二个线程的所有代码都有时间处理,包括strHandled.notify_one();
,这将导致信号会飞入宇宙而第一个线程永远不会知道它是。正确排列互斥锁以消除这种情况,然后挂起应该停止。但是为了使代码正确,您需要应用所有注释。
通过最低限度地更改原始代码,您可以获得如下内容:
明显的缺点:没有消费者实际消费的生产者流通知。因此,您必须为输出和通知保留互斥锁,并且由于虚假唤醒而导致“过度生产”也没有任何保护措施,但这个想法应该很清楚。
我怀疑仍然有必要向条件变量添加条件,以避免即使使用单个线程也会出现误报。让我们在列表中添加一个变量就绪标志: