例如,我有一些由多个线程同时计算的工作。
出于演示目的,工作在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();
}
}
};
答案 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::atomic,c++11 atomic operations或特定于平台的原子操作。
但是,您的代码似乎无需持有互斥锁即可访问mCounter
:
while ( mCounter < mTotal )
这是一个问题。您需要保持互斥锁才能访问共享状态。
您可能更喜欢使用这个成语:
获取锁定。
做测试和其他事情来决定我们是否需要做工作。
调整会计以反映我们决定做的工作。
释放锁定。做工作。获得锁定。
调整会计以反映我们已完成的工作。
循环回到第2步,除非我们完成了。
释放锁定。
答案 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