增加计数器时避免竞争条件

Posted

技术标签:

【中文标题】增加计数器时避免竞争条件【英文标题】:Avoid race condition when incrementing a counter 【发布时间】:2012-07-04 21:14:17 【问题描述】:

我以前就这个话题发过帖子,但到目前为止我还没有运气。我把它归结为我的一个坏问题。这次我做了一个简短的可编译示例,显示了我试图避免的不良行为。我希望这会受到赞赏。

问题是两个(或更多)线程被设置为运行同一个进程,它们的“id”决定了它们操作变量数据的哪一部分。目前两个线程都会更新计数器。

当前的输出是这样的,

tid = 0, var[tid] = 0
tid = 0, var[tid] = 1
tid = 0, var[tid] = 2
tid = 0, var[tid] = 3
tid = 0, var[tid] = 4
tid = 0, var[tid] = 5
tid = 0, var[tid] = 6
tid = 0, var[tid] = 7
tid = 0, var[tid] = 8
tid = 0, var[tid] = 9
tid = 1, var[tid] = 0
Press any key to continue . . .

想要的输出应该是这样的……

tid = 0, var[tid] = 0
tid = 1, var[tid] = 0
tid = 0, var[tid] = 1
tid = 1, var[tid] = 1
tid = 0, var[tid] = 2
tid = 1, var[tid] = 2
tid = 0, var[tid] = 3
tid = 1, var[tid] = 3 etc.

我们将不胜感激任何指导。

编辑:我已经使用按预期工作的代码更新了答案。

[注意这里效率很重要,我想尽快完成流程]

#include <iostream>  
#include <boost/thread.hpp>

int var[2];
int mT;
int mTotalSamples;
boost::mutex mCountMutex;
boost::thread *threadMap[2];

using namespace std;

void process()

    int tid = 1;

    // sleep for 1 seconds - just to make sure threadMap 
    // has been assigned (only ncessary for this demo).
    boost::this_thread::sleep(boost::posix_time::seconds(1));

    if (threadMap[0]->get_id() == boost::this_thread::get_id()) tid = 0;

    while ( mT < mTotalSamples ) 
    
        // perform processing
        var[tid] = mT; 
        // processing complete

        mCountMutex.lock(); // (a thread waits to aquire mutex)
        cout << "tid = " << tid << ", var[tid] = " << var[tid] << endl;
        mT++;           // How to stop both threads incrementing this?      
        mCountMutex.unlock();       
       


int main()

    boost::thread_group threads;

    mT = 0;
    mTotalSamples = 10;

    threadMap[0] = threads.create_thread( boost::bind(&process) );
    threadMap[1] = threads.create_thread( boost::bind(&process) );

    threads.join_all();

    return 0;

【问题讨论】:

听起来您希望线程能够交错访问互斥锁。但是您的代码中没有任何内容可以强制执行此操作。如果您希望操作以特定顺序开始,那么线程可能不是解决方案... @OliCharlesworth 互斥锁不是问题,每个线程应该只执行一行,var[tid] = mT;当计数器增加一次时。但是,老实说,我认为使用 cout 可能会混淆问题。 为了保护计数器,您可以使用互锁操作,例如 Windows 上的LONG __cdecl InterlockedIncrement( LONG volatile *Addend ); 。你真的需要保护你的var 变量吗? 尝试安排两个线程相互协作并没有多大意义,您不妨忘记线程,只需按程序/手动安排操作。在切换到另一个线程之前,线程会被赋予一定的执行时间。在那个时候,线程可能会完成比您想要的更多的工作,并且强制一个线程让步给另一个线程可能会影响调度程序的效率,从而影响您的应用程序的整体性能。您应该真正考虑是否需要线程来实现您的目的。也许他们是,也许不是,但请考虑一下 @dreamlax 谢谢,我知道这个例子有点做作,因为我为了使问题易于理解而进行了简化。实际上,一个线程可能需要几秒钟的时间,实际上我会执行与内核一样多的线程,并在这些线程之间平均分配“var”的更新,从而减少完成一个循环的时间.线程绝对适用于此目的。 【参考方案1】:

从您的预期输出来看,您希望线程在每次更新后同步。 boost 库提供了 boost::barrier,如果你在 process 的 while 循环的开头或结尾放置一个 wait ,应该可以解决问题。

#include <iostream>  
#include <boost/thread.hpp>

int var[2];
int mT;
int mTotalSamples;
boost::mutex mCountMutex;
boost::thread *threadMap[2];
boost::barrier bar(2);

using namespace std;

void process()

    int tid = 1;        

    // sleep for 2 seconds - just to make sure threadMap 
    // has been assigned (only ncessary for this demo).
    boost::this_thread::sleep(boost::posix_time::seconds(2));

    if (threadMap[0]->get_id() == boost::this_thread::get_id()) tid = 0;

    while ( mT < mTotalSamples ) 
    
        // perform processing
        var[tid] = mT; 
        // processing complete


        bar.wait();
        if (threadMap[0]->get_id() == boost::this_thread::get_id())
        
            mT++;               
            cout << "var[0] = " << var[0] << endl;
            cout << "var[1] = " << var[1] << endl;                      
                   
        bar.wait();
       


int main()

    boost::thread_group threads;

    mT = 0;
    mTotalSamples = 10;

    threadMap[0] = threads.create_thread( boost::bind(&process) );
    threadMap[1] = threads.create_thread( boost::bind(&process) );

    threads.join_all();

    return 0;

【讨论】:

@AlexS:好的,我已经批准了您的编辑,谢谢!顺便说一句,在您的应用程序中,是否真的有必要对bar.wait() 进行两次调用?我原以为循环顶部的一个就足够了。 嗯,我想是的,但我可能错了。没有它,我认为线程 2 有可能做到 var[tid] = mT;在线程 1 设法增加 mT 之前。第二道屏障确保这绝对不会发生。 @AlexS:你能验证一下吗?诸如屏障之类的同步点会显着降低并行性能,因此您应该尽可能少地使用它们! 我认为有两个是必要的,但只有将处理量增加到实际大小时才会清楚。即如果 var[0] 代表 500 万次操作,而 var[1] 代表 2000 万次操作,那么线程 1 会在线程 2 完成工作之前递增计数器。 @AlexS:好的,但是在循环的底部(即在计数器增量之后)设置屏障是否就足够了?【参考方案2】:

在 process() 中将此 int mT; 设为本地 - 不是全局的。 或者您需要int mT[2]; 那么你就不需要互斥锁了。

【讨论】:

感谢您的评论,尽管添加第二个计数器对我的需求没有帮助。同样,这完全取决于我将我的问题淡化为一个更简单的问题,为了使问题可读,上下文有些丢失。

以上是关于增加计数器时避免竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

Gorm原子更新到增量计数器

基于具有序列的条件递增计数器

JavaScript 事件处理的竞争条件?

fpga常用技巧设计总结

我们如何在没有条件的情况下递增然后递减计数器?

缓慢的 MySQL 查询:有没有办法避免对左连接的每一行进行条件选择计数?