我正在努力实现线程安全的引用计数队列。我的想法是,我有许多任务,每个任务维护拥有队列的任务管理器的shared_ptr。这是应该遇到相同问题的最小实现:

#include <condition_variable>
#include <deque>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>

namespace {

class TaskManager;

struct Task {
    std::function<void()> f;
    std::shared_ptr<TaskManager> manager;
};

class Queue {
  public:
    Queue()
        : _queue()
        , _mutex()
        , _cv()
        , _running(true)
        , _thread([this]() { sweepQueue(); })
    {
    }

    ~Queue() { close(); }

    void close() noexcept
    {
        try {
            {
                std::lock_guard<std::mutex> lock(_mutex);
                if (!_running) {
                    return;
                }
                _running = false;
            }
            _cv.notify_one();
            _thread.join();
        } catch (...) {
            std::cerr << "An error occurred while closing the queue\n";
        }
    }

    void push(Task&& task)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _queue.emplace_back(std::move(task));
        lock.unlock();
        _cv.notify_one();
    }

  private:
    void sweepQueue() noexcept
    {
        while (true) {
            try {
                std::unique_lock<std::mutex> lock(_mutex);
                _cv.wait(lock, [this] { return !_running || !_queue.empty(); });

                if (!_running && _queue.empty()) {
                    return;
                }

                if (!_queue.empty()) {
                    const auto task = _queue.front();
                    _queue.pop_front();
                    task.f();
                }
            } catch (...) {
                std::cerr << "An error occurred while sweeping the queue\n";
            }
        }
    }

    std::deque<Task> _queue;
    std::mutex _mutex;
    std::condition_variable _cv;
    bool _running;
    std::thread _thread;
};

class TaskManager : public std::enable_shared_from_this<TaskManager> {
  public:
    void addTask(std::function<void()> f)
    {
        _queue.push({ f, shared_from_this() });
    }

  private:
    Queue _queue;
};

}  // anonymous namespace

int main(void)
{
    const auto manager = std::make_shared<TaskManager>();
    manager->addTask([]() { std::cout << "Hello world\n"; });
}


我发现的问题是,在极少数情况下,队列将尝试在sweepQueue方法中调用其自己的析构函数。经过进一步检查,似乎最后一个任务出队后,TaskManager上的引用计数为零。如何在不调用析构函数的情况下安全地维护引用计数?

更新:该示例未阐明std::shared_ptr<TaskManager>中是否需要Task。这是一个用例示例,该用例应说明此看似不必要的所有权周期的必要性。

std::unique_ptr<Task> task;
{
    const auto manager = std::make_shared<TaskManager>();
    task = std::make_unique<Task>(someFunc, manager);
}
// Guarantees manager is not destroyed while task is still in scope.

最佳答案

这里的所有权层次结构是TaskManager拥有Queue,而Queue拥有TasksTasks维护指向TaskManager的共享指针会创建一个所有权周期,这似乎在这里没有用处。

这是所有权的根本原因。 QueueTaskManager拥有,因此Queue可以具有指向TaskManager的普通指针,并将该指针传递给Task中的sweepQueue。您完全不需要在std::shared_pointer<TaskManager>中的Task

07-25 20:36