std::thread::join在析构函数中挂起

3

我有以下代码,它在专用线程上运行函数。除了析构函数之外它完美工作,thread_.join()的调用不返回。我正在使用VS2013 Express。

我应该如何更改代码以确保线程能正确加入(join)?

#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>

namespace
{
    class main_thread
    {
    public:
        static auto instance() -> main_thread&
        {
            static main_thread instance_;
            return instance_;
        }
        auto enque(std::function<void()> func) -> void
        {
            {
                std::lock_guard<std::mutex> lock{ mutex_ };
                queue_.push_back(func);
            }
            condition_.notify_one();
        }
    private:
        main_thread()
        {
            continue_.test_and_set();
            thread_ = std::thread{ std::bind(std::mem_fn(&main_thread::run), this) };
        }
        ~main_thread()
        {
            continue_.clear();
            condition_.notify_all();
            if (thread_.joinable())
            {
                thread_.join();
            }
        }
        main_thread(const main_thread &other) = delete;
        main_thread(main_thread &&other) = delete;
        main_thread& operator=(const main_thread &other) = delete;
        main_thread& operator=(main_thread &&other) = delete;

        auto run() -> void
        {
            while (continue_.test_and_set())
            {
                auto lock = std::unique_lock<std::mutex>{ mutex_ };
                //condition_.wait_for(lock, std::chrono::milliseconds(1));
                condition_.wait(lock);
                for (auto &func : queue_)
                {
                    func();
                }
                queue_.clear();
            }
        }

        std::condition_variable condition_;
        std::mutex mutex_;
        std::vector<std::function<void()>> queue_;
        std::thread thread_;
        std::atomic_flag continue_;
    };
}

auto on_main_thread(std::function<void()> func) -> void
{
    main_thread::instance().enque(std::move(func));
}

auto on_main_thread_sync(std::function<void()> func) -> void
{
    bool done{ false };
    on_main_thread([&]{
        func();
        done = true;
    });
    while (!done);
}

这段代码唯一的作用是:
int main()
{
    on_main_thread([]{});
}

这可以避免on_main_thread_sync中的竞争问题,但仍然会在~main_thread中出现锁定。 Visual Studio显示有2个线程,但都不在main_thread::run中,所以我不理解发生了什么。 该函数已正确退出,但由于某种原因线程没有结束。


1
on_main_thread_sync 中的 done 存在数据竞争,它应该改为 std::atomic<bool>。我不确定这是唯一的问题,但它是一个问题。 - Casey
我同意那是一个问题。当我发布这个时,我注意到了它。然而,我不认为它导致了我遇到的特定问题。 - Graznarak
可能是 std::thread.join() 死锁 的重复问题。 - Robert Jørgensgaard Engdahl
标记为重复。我不确定这是否是正确的方法,但您似乎遇到了与此人相同的问题:https://dev59.com/0GQm5IYBdhLWcg3w-i9L - Robert Jørgensgaard Engdahl
这确实解决了问题。这是一个微软特定的问题,还是 GCC 和/或 Clang 也会出现同样的情况? - Graznarak
这似乎是一个已知的Visual Studio bug。http://connect.microsoft.com/VisualStudio/feedback/details/747145/ - Graznarak
2个回答

1

在关键代码段中不应调用外部代码,否则很容易导致死锁。

如果您在调试器中暂停执行,可能会看到一个或多个线程正在等待获取_mutex。

如果从func()调用的任何代码尝试enqueue(),则将无法再次获取_mutex上的unique_lock。

尝试在condition_variable wait结束后释放锁。 作为测试,您可以添加额外的作用域以查看是否有帮助:

while (continue_.test_and_set())
{
    std::vector<std::function<void()>> queue;
    {
        auto lock = std::unique_lock<std::mutex>{ mutex_ };
        //condition_.wait_for(lock, std::chrono::milliseconds(1));
        condition_.wait(lock);
        queue.swap(queue_);
    }
    for (auto &func : queue)
    {
        func();
    }
}

дљ†еЬ®mutex_дєЛе§ЦиЃњйЧЃдЇЖqueue_пЉМеѓЉиЗідЇЖжХ∞жНЃзЂЮдЇЙгАВ - Casey
谢谢注意到这个问题,@Casey。我已经更新了解决方案,在获取mutex_的时候只访问queue_。 - Phillip Kinkade
这是另一个我错过的问题。感谢你注意到了它。我在工作中遇到过这种死锁情况,你会认为我现在应该知道得更好了。 - Graznarak
当你在调试器中暂停时,除了加入(join)之外,你的线程还在等待什么? - Phillip Kinkade
...或者堆栈可能显示另一个线程在enqueue()中等待获取互斥锁。也许你可以在这里发布其他/卡住的线程的回溯? - Phillip Kinkade
显示剩余3条评论

1
您的代码在关闭时可能会出现潜在的死锁。以下交错顺序是可能的:
主线程 运行中的线程 检查continue_,发现它为真 设置continue_=false 通知条件变量 join 等待条件变量
为了避免这种情况,您需要原子地进行条件检查和cv等待。最简单的方法是使用mutex_保护continue_(请参见Coliru的实时演示)。
class main_thread
{
public:
    static auto instance() -> main_thread&
    {
        static main_thread instance_;
        return instance_;
    }
    auto enque(std::function<void()> func) -> void
    {
        {
            std::lock_guard<std::mutex> lock{ mutex_ };
            queue_.push_back(func);
        }
        condition_.notify_one();
    }
private:
    main_thread() : continue_{true}
    {
        thread_ = std::thread{ &main_thread::run, this };
    }
    ~main_thread()
    {
        {
            std::lock_guard<std::mutex> lock{ mutex_ };
            continue_ = false;
        }
        condition_.notify_all();
        if (thread_.joinable())
        {
            thread_.join();
        }
    }

    auto run() -> void
    {
        std::unique_lock<std::mutex> lock{ mutex_ };
        while(continue_)
        {
            if(queue_.empty())
            {
                condition_.wait(lock);
                continue;
            }

            std::vector<std::function<void()>> queue;
            queue.swap(queue_);
            lock.unlock();
            for (auto &func : queue)
            {
                func();
            }
            lock.lock();
        }
    }

    std::condition_variable condition_;
    std::mutex mutex_;
    std::vector<std::function<void()>> queue_;
    bool continue_;
    std::thread thread_;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接