我有一堆线程正在处理多个数据项。线程必须按照我将数据传递给线程的相同顺序输出结果。那就是:
Thread #1: give data - start processing
Thread #2: give data - start processing
Thread #3: give data - start processing
...
Thread #n: give data - start processing
应该以与传递给线程的数据相同的顺序检索结果,无论哪个线程首先完成处理。即:
Thread #1: put data
Thread #2: put data
...
为了区分线程并管理它们,我给每个线程一个ID (0,1,2,...,n)
。我正在使用ID为每个线程分配数据,以便它可以处理它。
for(int i=0; i<thread_count; i++)
give_data(i); // i is id and the function knows where to get data from
我希望线程共享一个令牌,该令牌决定了预期产生结果的线程。所有线体都是相同的,正文如下:
while(true){
auto data = get_data();
result = process_data(data);
while(token != this_id) spin;
put_data(result); // this is a synchronized call
update_token(token);
}
我的问题来自token
。我首先尝试了一个正常的引用(int & token
),它显然无法工作(我没有预料到)。无论如何,我使用了一个静态变量,并且线程并不总是得到最新的。我很惊讶地看到一个主线支配一切。每当线程更新令牌时,它就会失去允许另一个线程放置其结果等等。但是,我有一个线程占主导地位,好像令牌始终设置为自己的ID而不是更新。
如果我不得不猜测我会说它是一个缓存问题。但是,我不确定。
无论如何,我正在考虑使用std::atomic<int>
作为我的令牌。会有用吗?如果没有,我还应该考虑做什么?什么是同步这些线程的更好方法?
额外:这感觉就像一个糟糕的设计,我不知道如何做得更好。任何建议将非常感谢。
答案 0 :(得分:6)
无论如何,我使用了一个静态变量,并且线程并不总是得到最新的。我很惊讶地看到一个主线支配着一切
是的,多个线程访问相同的非同步值,其中至少有一个写入它是数据竞争,这是根据C ++标准的未定义行为。任何事情都可能发生。
我正在考虑使用std :: atomic作为我的令牌。它会起作用吗?
是。这可以防止令牌上的任何数据竞争。我在你的伪代码中没有看到任何其他直接问题,所以从这个角度看它看起来不错。
这感觉就像一个糟糕的设计,我不知道如何做得更好。任何建议将非常感谢。
整个设计确实看起来有些奇怪,但如果有更简单的表达方式,它取决于您的线程库。例如,使用OpenMP,您可以在一次传递中执行此操作(give_data
和get_data
背后的逻辑太不明确,无法完成此操作):
#pragma omp parallel
{
int threadCount = omp_get_num_threads();
#pragma omp single
for (int i = 0; i < threadCount; ++i)
give_data(i);
#pragma omp ordered for ordered schedule(static)
for (int i = 0; i < threadCount; ++i)
{
auto data = get_data();
result = process_data(data);
#pragma omp ordered
put_data(result); // this is a synchronized call
}
}
ordered
指令强制put_data
调用以完全相同的顺序(逐个)执行,就像循环是串行的一样,而线程仍然可以执行先前的数据处理并行。
如果 想要做的就是使一个大的数据处理循环与有序写入并行,那么使用OpenMP实际上可能会更容易:
#pragma omp parallel for ordered schedule(static)
for (int i = 0; i < dataItemCount; ++i)
{
auto data = get_data(i); // whatever this would entail
auto result = process_data(data);
#pragma omp ordered
put_data(result); // this is a synchronized call
}
看起来您不需要按顺序排列数据项,但如果您确实这样做,那么这种方法就不会那么容易,因为每个有序循环只能有一个有序的部分。
答案 1 :(得分:2)
Max的答案很棒。如果我有能力使用OpenMP给我的时间我会这样做。但是,我不是这就是为什么我将这个答案发给我的问题。
在我之前的设计中,它依赖于线程来相互同步,这似乎不是最好的主意,因为这么多可能会出错。相反,我决定让经理同步他们的结果(我从Max的最后一段代码片段中得到了这个想法)。
void give_threads_data(){
vector<pair<data, promise<result>*> promises(threads.size());
vector<future<result>> futures(threads.size());
for(int i=0; i<threads.size(); i++){
data d = get_data();
threads[i].put_data(d, promises[i]);
futures[i] = promises[i].get_future();
}
for(int i=0; i<futures.size(); i++){
result = futures[i].get();
// handle result
}
}
通过这种方式,我能够以与将它们发送到线程相同的方式获得结果。线体变得更清洁:
void thread_body(){
while(true){
pair<data, promise<result>*> item = queue.get(); // blocking call
data d = item.first;
promise<result>* promise = item.second;
result r = process_data(d);
promise->set_value(r);
}
}
没有比赛,结果很完美。下次我在做线程时我会考虑OpenMP。