用于线程同步的std :: atomic vs static变量

时间:2018-04-24 15:54:13

标签: c++ multithreading synchronization atomic

我有一堆线程正在处理多个数据项。线程必须按照我将数据传递给线程的相同顺序输出结果。那就是:

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>作为我的令牌。会有用吗?如果没有,我还应该考虑做什么?什么是同步这些线程的更好方法?

额外:这感觉就像一个糟糕的设计,我不知道如何做得更好。任何建议将非常感谢。

2 个答案:

答案 0 :(得分:6)

  

无论如何,我使用了一个静态变量,并且线程并不总是得到最新的。我很惊讶地看到一个主线支配着一切

是的,多个线程访问相同的非同步值,其中至少有一个写入它是数据竞争,这是根据C ++标准的未定义行为。任何事情都可能发生。

  

我正在考虑使用std :: atomic作为我的令牌。它会起作用吗?

是。这可以防止令牌上的任何数据竞争。我在你的伪代码中没有看到任何其他直接问题,所以从这个角度看它看起来不错。

  

这感觉就像一个糟糕的设计,我不知道如何做得更好。任何建议将非常感谢。

整个设计确实看起来有些奇怪,但如果有更简单的表达方式,它取决于您的线程库。例如,使用OpenMP,您可以在一次传递中执行此操作(give_dataget_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。