正确使用原子<T>

Correctly using atomic<T>

本文关键字:lt gt      更新时间:2023-10-16

我目前正在为基于https://github.com/nbsdx/ThreadPool.这是我修改过的课程:

class ThreadPool
{
public:
    explicit ThreadPool(int threadCount) :
        _jobsLeft(0),
        _bailout(false)
    {
        _threads = std::vector<std::thread>(threadCount);
        for (int index = 0; index < threadCount; ++index)
        {
            _threads[index] = std::move(std::thread([this]
                {
                    this->Task();
                }));
        }
    }
    void AddJob(std::function<void(void)> job)
    {
        {
            std::lock_guard<std::mutex> lock(_queueMutex);
            _queue.emplace(job);
        }
        {
            std::lock_guard<std::mutex> lock(_jobsLeftMutex);
            ++_jobsLeft;
        }
        _jobAvailableVar.notify_one();
    }
    void JoinAll()
    {
        if (!_bailout)
        {
            _bailout = true;
            _jobAvailableVar.notify_all();
            for (auto& x : _threads)
            {
                if (x.joinable())
                {
                    x.join();
                }
            }
        }
    }
    void WaitAll()
    {
        std::unique_lock<std::mutex> lock(_jobsLeftMutex);
        if (_jobsLeft > 0)
        {
            _waitVar.wait(lock, [this]
                          {
                              return _jobsLeft == 0;
                          });
        }
    }
private:
    void Task()
    {
        while (!_bailout)
        {
            std::function<void(void)> job;
            {
                std::unique_lock<std::mutex> lock(_queueMutex);
                _jobAvailableVar.wait(lock, [this]
                                      {
                                          return _queue.size() > 0 || _bailout;
                                      });
                if (_bailout)
                {
                    return;
                }
                job = _queue.front();
                _queue.pop();
            }
            job();
            {
                std::lock_guard<std::mutex> lock(_jobsLeftMutex);
                --_jobsLeft;
            }
            _waitVar.notify_one();
        }
    }
    std::vector<std::thread> _threads;
    std::queue<std::function<void(void)>> _queue;
    int _jobsLeft;
    std::atomic<bool> _bailout;
    std::condition_variable _jobAvailableVar;
    std::condition_variable _waitVar;
    std::mutex _jobsLeftMutex;
    std::mutex _queueMutex;
};

我对以下内容感到困惑,如果有任何建议,我将不胜感激:

  • 如何使_bailout的使用真正原子化?我玩了atomic_flag,但在这种情况下无法使其发挥作用
  • 在没有所有这些锁的情况下,使用atomic<int>是否可以使_jobsLeft正常工作?我对原始线程池有一个问题,在极少数情况下WaitAll()不会正确返回。它现在运行得很好,但我觉得它浪费了性能

编辑:此处提供带普通锁的最终版本:https://github.com/stfx/ThreadPool2

看看原子方法:http://en.cppreference.com/w/cpp/atomic/atomic

您应该使用load方法以原子方式读取该值。但是,请注意以下内容不是原子性的。

if (!_bailout)
{
    _bailout = true;

有一种方法可以执行这些比较和价值交换。请参见compare_exchange_weak方法。对于jobCount,可以使用atomic。++--是原子。

但是,请注意,原子只是一个单独的方法调用,调用后情况可能会发生变化。您仍然需要一个同步的队列,为此您需要一个锁。它不需要是标准的操作系统锁,您可以使用原子变量创建锁(使用storecompare_exchange_weak方法)。请参阅以下帖子:https://webkit.org/blog/6161/locking-in-webkit/