繁体   English   中英

如何使用std :: atomic实现无锁计数器?

[英]How to implement lock-free counter with std::atomic?

在我的程序中,多个线程(检查器)请求网页,如果这些页面包含一些数据,则另一个线程(消费者)处理数据。 我只需要预定义的消费者数来开始处理(不是全部)。 我尝试使用std :: atomic counter和fetch_add来限制工作的消费者数量。 但是,虽然计数器保持在一定范围内,但消费者获得相同的计数器值,并且实际处理消费者数量超过限制 行为取决于处理持续时间。 简化代码包含sleep_for而不是获取页面和处理页面功能。

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>

class cConsumer
{
public:

    cConsumer::cConsumer(
        const size_t aNumber,
        std::atomic<bool> &aFire,
        std::atomic<size_t> &aCounter) :
        mNumber(aNumber),
        mFire(aFire),
        mCounter(aCounter){}

    void cConsumer::operator ()()
    {
        while (true)
        {
            while (!mFire.load()) std::this_thread::sleep_for(mMillisecond);

            size_t vCounter = mCounter.fetch_add(1);
            if (vCounter < 5)
            {
                std::cout << "      FIRE! consumer " << mNumber << ", counter " << vCounter << "\n";
                std::this_thread::sleep_for(mWorkDuration);
            }
            if (vCounter == 5)
            {
                mFire.store(false);
                mCounter.store(0);
            }
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mWorkDuration;

    const size_t mNumber;

    std::atomic<bool> &mFire;
    std::atomic<size_t> &mCounter;
};

const std::chrono::milliseconds 
    cConsumer::mMillisecond(1),
    cConsumer::mWorkDuration(1300);

class cChecker
{
public:

    cChecker(
        const size_t aNumber,
        std::atomic<bool> &aFire) :
        mNumber(aNumber),
        mFire(aFire),
        mStep(1){ }

    void cChecker::operator ()()
    {
        while (true)
        {
            while (mFire.load()) std::this_thread::sleep_for(mMillisecond);

            std::cout << "checker " << mNumber << " step " << mStep << "\n";
            std::this_thread::sleep_for(mCheckDuration);
            if (mStep % 20 == 1) mFire.store(true);         
            mStep++;
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mCheckDuration;

    const size_t mNumber;

    size_t mStep;

    std::atomic<bool> &mFire;
};

const std::chrono::milliseconds 
    cChecker::mMillisecond(1),
    cChecker::mCheckDuration(500);

void main()
{
    std::atomic<bool> vFire(false);
    std::atomic<size_t> vCounter(0);

    std::thread vConsumerThreads[16];

    for (size_t i = 0; i < 16; i++)
    {
        std::thread vConsumerThread((cConsumer(i, vFire, vCounter)));
        vConsumerThreads[i] = std::move(vConsumerThread);       
    }

    std::chrono::milliseconds vNextCheckerDelay(239);

    std::thread vCheckerThreads[3];

    for (size_t i = 0; i < 3; i++)
    {
        std::thread vCheckerThread((cChecker(i, vFire)));
        vCheckerThreads[i] = std::move(vCheckerThread);
        std::this_thread::sleep_for(vNextCheckerDelay);
    }

    for (size_t i = 0; i < 16; i++) vConsumerThreads[i].join();

    for (size_t i = 0; i < 3; i++) vCheckerThreads[i].join();
}

输出示例(部分)

...
checker 1 step 19
checker 0 step 20
checker 2 step 19
checker 1 step 20
checker 0 step 21
checker 2 step 20
checker 1 step 21
      FIRE! consumer 10, counter 0
      FIRE! consumer 13, counter 4
      FIRE! consumer 6, counter 1
      FIRE! consumer 0, counter 2
      FIRE! consumer 2, counter 3
checker 0 step 22
checker 2 step 21
      FIRE! consumer 5, counter 3
      FIRE! consumer 7, counter 4
      FIRE! consumer 4, counter 1
      FIRE! consumer 15, counter 2
      FIRE! consumer 8, counter 0
checker 1 step 22
      FIRE! consumer 9, counter 0
      FIRE! consumer 11, counter 1
      FIRE! consumer 3, counter 2
      FIRE! consumer 14, counter 3
      FIRE! consumer 1, counter 4
checker 0 step 23
checker 2 step 22
checker 1 step 23
checker 2 step 23
checker 0 step 24
checker 1 step 24

我找到了一个有效但不优雅的解决方案:等待所有消费者尝试工作并了解火灾已经消失。

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>

class cConsumer
{
public:

    cConsumer::cConsumer(
        const size_t aNumber,
        const size_t aConsumerCount,
        std::atomic<bool> &aFire,
        std::atomic<size_t> &aCounter) :
        mNumber(aNumber),
        mConsumerCount(aConsumerCount),
        mFire(aFire),
        mCounter(aCounter){}

    void cConsumer::operator ()()
    {
        while (true)
        {
            while (!mFire.load()) std::this_thread::sleep_for(mMillisecond);

            const size_t vCounter = mCounter.fetch_add(1);

            if (vCounter < 5)
            {
                std::cout << "      FIRE! consumer " << mNumber << ", counter " << vCounter << "\n";
                std::this_thread::sleep_for(mWorkDuration); //stub for process function
            }

            if (vCounter >= 5)
            {
                std::this_thread::sleep_for(mWorkDuration); //wait for other threads to increase counter
                std::this_thread::sleep_for(mWorkDuration); //double wait for long processing
                mFire.store(false);
            }

            if (vCounter == mConsumerCount)
            {               
                mCounter.store(0);
            }
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mWorkDuration;

    const size_t 
        mNumber,
        mConsumerCount;

    std::atomic<bool> &mFire;
    std::atomic<size_t> &mCounter;
};

const std::chrono::milliseconds 
    cConsumer::mMillisecond(1),
    cConsumer::mWorkDuration(1300);

class cChecker
{
public:

    cChecker(
        const size_t aNumber,
        std::atomic<bool> &aFire) :
        mNumber(aNumber),
        mFire(aFire),
        mStep(1){ }

    void cChecker::operator ()()
    {
        while (true)
        {
            while (mFire.load()) std::this_thread::sleep_for(mMillisecond);

            std::cout << "checker " << mNumber << " step " << mStep << "\n";
            std::this_thread::sleep_for(mCheckDuration);
            if (mStep % 20 == 1) mFire.store(true);         
            mStep++;
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mCheckDuration;

    const size_t mNumber;

    size_t mStep;

    std::atomic<bool> &mFire;
};

const std::chrono::milliseconds 
    cChecker::mMillisecond(1),
    cChecker::mCheckDuration(500);

void main()
{
    std::atomic<bool> vFire(false);
    std::atomic<size_t> vCouter(0);

    std::thread vConsumerThreads[16];

    for (size_t i = 0; i < 16; i++)
    {
        vConsumerThreads[i] = std::move(std::thread(cConsumer(i, 16, vFire, vCouter)));
    }

    std::chrono::milliseconds vNextCheckerDelay(239);

    std::thread vCheckerThreads[3];

    for (size_t i = 0; i < 3; i++)
    {
        vCheckerThreads[i] = std::move(std::thread(cChecker(i, vFire)));
        std::this_thread::sleep_for(vNextCheckerDelay);
    }

    for (size_t i = 0; i < 16; i++) vConsumerThreads[i].join();

    for (size_t i = 0; i < 3; i++) vCheckerThreads[i].join();

我认为存在更好的解决方案。

这里发生了什么?

运气不错,一旦你着火,可能会有更多的工人通过这条线:

    while(!mFire.load()) std::this_thread::sleep_for(mMillisecond);

假设有10个工作人员醒来,那个计数器为0.每10名工人将执行此操作:

    size_t vCounter = mCouter.fetch_add(1);

现在,10个工人中的每个工人都有1到11之间的不同计数器。第5个工作人员将执行if子句:

        if(vCounter < 5)

具有更高计数器的任何线程将继续。 其中第6个线程,将重置火并重置计数器:

        if(vCounter == 5)
        {
            mFire.store(false);
            mCouter.store(0);
            cout << "RESET!!!!!! by consume "<<mNumber << endl; // useful to understand
        }

所有这些谜题线程将继续循环等待下一次火灾。

但是现在坏事可能会发生,因为你有一些工作人员还在工作,而且你有一堆跳棋等着再次放火:

while(mFire.load()) std::this_thread::sleep_for(mMillisecond);
...   // now that fire is reset, they will go on

有些可以达到以下几行:

        if(mStep % 20 == 1) {
            mFire.store(true); 
            cout << "SET FIRE" << endl;   // to make the problem visual
        }

当原子计数器为0时,除了仍在运行的工作之外,你将立即拥有5个新工作人员。

你能为这个做什么 ?

我不清楚你打算做什么:

  • 你想让每个新火有5名工人活动吗? 在这种情况下,就像你一样。 总工人总数可能超过5人。
  • 你想在任何时候让最多5名工人活跃吗? 在这种情况下,你永远不应该像你那样将工作器数量重置为0,但是你应该减少所有增加它的线程的计数器。 因此,conter将包含当前在fire处理部分中的线程数:

     while(true) { while(!mFire.load()) std::this_thread::sleep_for(mMillisecond); size_t vCounter = mCouter.fetch_add(1); // FIRE PROCESSING: INCREMENT COUNTER if(vCounter < 5) { std::cout << " FIRE! consumer " << mNumber << ", counter " << vCounter << "\\n"; std::this_thread::sleep_for(mWorkDuration); std::cout << " finished consumer "<< mNumber<<endl; } if(vCounter == 5) { mFire.store(false); //mCouter.store(0); cout << "RESET!!!!!! by consumer "<<mNumber << endl; } mCouter.fetch_sub(1); // END OF PROCESSING: DECREMENT COUNTER 

可能的解决方案是为消费者完成标志使用辅助数组。 当消费者完成处理时,它将真实存储到其完成的阵列单元格。 另一个控制线程扫描完成阵列,确保所有单元都为真,并重置程序状态。

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>

class cConsumer
{
public:

    cConsumer::cConsumer(
        const size_t aNumber,
        const size_t aFiresLimit,
        std::atomic<bool> &aFire,
        std::atomic<bool> &aDone,
        std::atomic<size_t> &aCounter) :
        mNumber(aNumber),
        mFiresLimit(aFiresLimit),
        mFire(aFire),
        mDone(aDone),
        mCounter(aCounter){}

    void cConsumer::operator ()()
    {
        while (true)
        {
            while (!mFire.load()) std::this_thread::sleep_for(mMillisecond);

            const size_t vCounter = mCounter.fetch_add(1);

            if (vCounter < mFiresLimit)
            {
                std::cout << "      FIRE! consumer " << mNumber << ", counter " << vCounter << "\n";
                std::this_thread::sleep_for(mWorkDuration); // instead real processing
            }   

            mDone.store(true);

            while (mDone.load()) std::this_thread::sleep_for(mMillisecond);
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mWorkDuration;

    const size_t 
        mNumber,
        mFiresLimit;

    std::atomic<bool> 
        &mFire,
        &mDone;

    std::atomic<size_t> &mCounter;
};

const std::chrono::milliseconds 
    cConsumer::mMillisecond(1),
    cConsumer::mWorkDuration(1300);

class cChecker
{
public:

    cChecker(
        const size_t aNumber,
        std::atomic<bool> &aFire) :
        mNumber(aNumber),
        mFire(aFire),
        mStep(1){ }

    void cChecker::operator ()()
    {
        while (true)
        {
            while (mFire.load()) std::this_thread::sleep_for(mMillisecond);

            std::cout << "checker " << mNumber << " step " << mStep << "\n";
            std::this_thread::sleep_for(mCheckDuration);
            if (mStep % 20 == 1) // dummy condition instead real checker function
            {
                mFire.store(true);
            }
            mStep++;
        }
    }

private:

    static const std::chrono::milliseconds 
        mMillisecond,
        mCheckDuration;

    const size_t mNumber;

    size_t mStep;

    std::atomic<bool> &mFire;
};

const std::chrono::milliseconds 
    cChecker::mMillisecond(1),
    cChecker::mCheckDuration(500);

class cController
{
public:

    cController(
        const size_t aConsumerCount,
        std::atomic<bool> &aFire,
        std::atomic<bool> * const aConsumersDone,
        std::atomic<size_t> &aCounter) :
        mConsumerCount(aConsumerCount),
        mFire(aFire),
        mConsumersDone(aConsumersDone),
        mCounter(aCounter){}

    void cController::operator ()()
    {
        while (true)
        {       
            while(!mFire.load()) std::this_thread::sleep_for(mMillisecond);

            bool vAllConsumersDone = false;

            while (!vAllConsumersDone)
            {
                size_t i = 0;
                while ((i < mConsumerCount) && (mConsumersDone[i].load())) i++;
                vAllConsumersDone = (i == mConsumerCount);
                std::this_thread::sleep_for(mMillisecond);
            }

            mFire.store(false);
            for (size_t i = 0; i < mConsumerCount; i++) mConsumersDone[i].store(false);
            mCounter.store(0);
        }
    }

private:

    const size_t mConsumerCount;

    static const std::chrono::milliseconds mMillisecond;

    std::atomic<bool> 
        &mFire,
        * const mConsumersDone;

    std::atomic<size_t> &mCounter;
};

const std::chrono::milliseconds cController::mMillisecond(1);

void main()
{
    static const size_t 
        vCheckerCount = 3,
        vConsumersCount = 16,
        vFiresLimit = 5;

    std::atomic<bool> vFire(false);

    std::atomic<bool> vConsumersDone[vConsumersCount];
    for (size_t i = 0; i < vConsumersCount; i++) vConsumersDone[i].store(false);

    std::atomic<size_t> vCounter(0);    

    std::thread vControllerThread(cController(vConsumersCount, vFire, vConsumersDone, vCounter));

    std::thread vConsumerThreads[vConsumersCount];

    for (size_t i = 0; i < vConsumersCount; i++)
    {
        vConsumerThreads[i] = std::move(std::thread(cConsumer(i, vFiresLimit, vFire, vConsumersDone[i], vCounter)));
    }

    std::chrono::milliseconds vNextCheckerDelay(239);

    std::thread vCheckerThreads[vCheckerCount];

    for (size_t i = 0; i < vCheckerCount; i++)
    {
        vCheckerThreads[i] = std::move(std::thread(cChecker(i, vFire)));
        std::this_thread::sleep_for(vNextCheckerDelay);
    }

    for (size_t i = 0; i < vConsumersCount; i++) vConsumerThreads[i].join();

    for (size_t i = 0; i < vCheckerCount; i++) vCheckerThreads[i].join();

    vControllerThread.join();
}

输出(部分)示例:

...
checker 2 step 19
checker 1 step 19
checker 0 step 19
checker 2 step 20
checker 0 step 20
checker 1 step 20
checker 2 step 21
checker 0 step 21
checker 1 step 21
      FIRE! consumer 11, counter 0
      FIRE! consumer 3, counter 2
      FIRE! consumer 4, counter 3
      FIRE! consumer 10, counter 4
      FIRE! consumer 14, counter 1
checker 0 step 22
checker 2 step 22
checker 1 step 22
checker 2 step 23
checker 0 step 23
checker 1 step 23
checker 2 step 24
checker 0 step 24

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM