安全的多线程计数器增量

时间:2012-05-05 18:27:45

标签: c++ multithreading algorithm boost boost-thread

例如,我有一些由多个线程同时计算的工作。

出于演示目的,工作在while循环内执行。在一次迭代中,每个线程执行其自己的工作部分,在下一次迭代开始之前,计数器应该递增一次。

我的问题是每个帖子都会更新计数器。

由于这似乎是一件相对简单的事情,我认为有一种“最佳实践”或常用方法可以解决这个问题吗?

以下是一些示例代码,用于说明问题并帮助讨论。 (我使用提升线程)

class someTask {
public:
    int mCounter; //initialized to 0
    int mTotal; //initialized to i.e. 100000
    boost::mutex cntmutex;                
    int getCount()
    {
            boost::mutex::scoped_lock lock( cntmutex );
            return mCount;
    }
    void process( int thread_id, int numThreads )
    {
        while ( getCount() < mTotal )
        {
            // The main task is performed here and is divided 
            // into sub-tasks based on the thread_id and numThreads

                            // Wait for all thread to get to this point

            cntmutex.lock();
            mCounter++;  // < ---- how to ensure this is only updated once?
            cntmutex.unlock();
        }
    }
};

4 个答案:

答案 0 :(得分:5)

我在这里看到的主要问题是你的理由太低了。因此,我将提出一个基于新的C ++ 11线程API的替代解决方案。

主要的想法是你基本上有一个时间表 - &gt;发送 - &gt;做 - &gt;收集 - &gt;循环程序。在你的例子中,你试图在do阶段推理所有这些,这很难。使用相反的方法可以更容易地表达您的模式。

首先,我们将自己的日常工作分开:

void process_thread(size_t id, size_t numThreads) {
    // do something
}

现在,我们可以轻松调用此例程:

#include <future>
#include <thread>
#include <vector>

void process(size_t const total, size_t const numThreads) {
    for (size_t count = 0; count != total; ++count) {
         std::vector< std::future<void> > results;

         // Create all threads, launch the work!
         for (size_t id = 0; id != numThreads; ++id) {
             results.push_back(std::async(process_thread, id, numThreads));
         }

         // The destruction of `std::future`
         // requires waiting for the task to complete (*)
    }
}

(*)见this question

您可以阅读有关std::async here的更多信息,简短的介绍是offered here(它们似乎与发布政策的效果有些矛盾,哦,好吧)。这里让实现决定是否创建操作系统线程更简单:它可以根据可用内核的数量进行调整。

注意删除共享状态如何简化代码。因为线程没有共享,我们不再需要明确地担心同步!

答案 1 :(得分:2)

您使用互斥锁保护计数器,确保没有两个线程可以同时访问计数器。您的另一个选择是使用Boost::atomicc++11 atomic operations或特定于平台的原子操作。

但是,您的代码似乎无需持有互斥锁即可访问mCounter

    while ( mCounter < mTotal )

这是一个问题。您需要保持互斥锁才能访问共享状态。

您可能更喜欢使用这个成语:

  1. 获取锁定。

  2. 做测试和其他事情来决定我们是否需要做工作。

  3. 调整会计以反映我们决定做的工作。

  4. 释放锁定。做工作。获得锁定。

  5. 调整会计以反映我们已完成的工作。

  6. 循环回到第2步,除非我们完成了。

  7. 释放锁定。

答案 2 :(得分:1)

您需要使用消息传递解决方案。 TBB或PPL等库更容易实现这一点。 PPL在Visual Studio 2010及更高版本中免费提供,TBB可以在英特尔的FOSS许可下免费下载。

concurrent_queue<unsigned int> done;
std::vector<Work> work; 
// fill work here
parallel_for(0, work.size(), [&](unsigned int i) {
    processWorkItem(work[i]);
    done.push(i);
});

它是无锁的,您可以使用外部线程监视done变量,以查看已完成的内容和内容。

答案 3 :(得分:1)

我想不同意大卫做多次锁定收购来完成这项工作。

Mutexes是昂贵的,并且有更多线程竞争mutex,它基本上回退到系统调用,这导致用户空间到内核空间上下文切换以及调用者线程( / s)被迫睡觉:因此很多开销。

因此,如果您使用的是多处理器系统,我强烈建议您使用自旋锁[1]。

所以我要做的是:

=&GT;摆脱范围锁定获取以检查条件。

=&GT;让你的计数器变得不稳定以支持上面的

=&GT;在while循环中,在获取锁定后再次检查条件。

class someTask {
 public:
 volatile int mCounter; //initialized to 0       : Make your counter Volatile
 int mTotal; //initialized to i.e. 100000
 boost::mutex cntmutex;                

 void process( int thread_id, int numThreads )
 {
    while ( mCounter < mTotal ) //compare without acquiring lock
    {
        // The main task is performed here and is divided 
        // into sub-tasks based on the thread_id and numThreads

        cntmutex.lock();
        //Now compare again to make sure that the condition still holds
        //This would save all those acquisitions and lock release we did just to 
        //check whther the condition was true.
        if(mCounter < mTotal)
        {
             mCounter++;  
        }

        cntmutex.unlock();
    }
 }
};

[1] http://www.alexonlinux.com/pthread-mutex-vs-pthread-spinlock