【问题标题】:Remove thread from vector upon thread completion线程完成后从向量中删除线程
【发布时间】:2017-09-13 01:16:37
【问题描述】:

我的 C++ 程序中有 vectorthreads。

std::vector<std::thread> threadList;

然后我创建一个线程并将其推送到vector

threadList.push_back(std::thread([]() { ... }));

lambda function 完成执行时,如何从threadList vector 中删除thread


编辑

我想出了一些解决方案;在线程lambda function 返回之前,它会遍历vector 以在ID 中与this_thread::get_id() 匹配。

通过 Visual Studio 逐行调试,我看到它在 ID 中找到了匹配项,并执行了 erase 函数,但是在执行 threadList.erase(threadList.begin() + index); 时,我在线程的解构函数处遇到了未处理的异常.

我写了一小段代码来复制这个错误。

vector<thread> threadList;

threadList.push_back(thread([]() {
    Sleep(1000);
    threadList.erase(threadList.begin());
}));

Sleep(2000);

//for_each(threadList.begin(), threadList.end(), mem_fn(&thread::detach));
//threadList.clear();

此代码生成下面的屏幕截图。

【问题讨论】:

  • lambda 可以在退出之前从向量中删除std::thread(这意味着保护vector 免受并发访问,例如使用std::mutex)。或者,您可以让 lambda 以某种方式向拥有 vector 的线程发出信号,并让该线程在有时间时删除 std::thread。或者,您可以简单地拥有拥有线程(或另一个监控线程)join()std::thread,并在完成时将其删除。
  • join() 首先线程,然后以完全相同的方式将其从向量中删除,100% 相同的方式您将从任何其他向量中删除某些内容。向量在添加或删除值方面没有任何不同,只是因为它包含std::threads。
  • std::vector::erase()
  • 我已经更新了我的帖子
  • 您对push_back(在“编辑”部分)所做的操作无效;在多个线程中修改一个对象。第一条评论已经提到了

标签: c++ multithreading vector lambda


【解决方案1】:

一种选择是让 lambda 在退出时异步删除线程。例如:

std::vector<std::thread> threadList;
std::mutex threadMutex;

... 

void removeThread(std::thread::id id)
{
    std::lock_guard<std::mutex> lock(threadMutex);
    auto iter = std::find_if(threadList.begin(), threadList.end(), [=](std::thread &t) { return (t.get_id() == id); });
    if (iter != threadList.end())
    {
        iter->detach();
        threadList.erase(iter);
    }
}

... 

{
    std::lock_guard<std::mutex> lock(threadMutex);
    threadList.push_back(
        std::thread([]() {
            ...
            std::async(removeThread, std::this_thread::get_id());
        })
    );
}

或者:

std::vector<std::thread> threadList;
std::mutex threadMutex;

... 

void removeThread(std::thread::id id)
{
    std::lock_guard<std::mutex> lock(threadMutex);
    auto iter = std::find_if(threadList.begin(), threadList.end(), [=](std::thread &t) { return (t.get_id() == id); });
    if (iter != threadList.end())
    {
        iter->join();
        threadList.erase(iter);
    }
}

... 

{
    std::lock_guard<std::mutex> lock(threadMutex);
    threadList.push_back(
        std::thread([]() {
            ...
            std::thread(removeThread, std::this_thread::get_id()).detach();
        })
    );
}

或者:

std::vector<std::thread> threadList;
std::mutex threadMutex;

std::list<std::thread::id> threadFreeList;
std::mutex threadFreeMutex;
std::condition_variable threadFreeCV;

std::thread monitor([]() {
    while (... /* app is not terminated */)
    {
        std::unique_lock<std::mutex> lock(threadFreeMutex);
        threadFreeCV.wait(lock);

        std::lock_guard<std::mutex> lock2(threadMutex);
        auto iter = threadFreeList.begin();
        while (iter != threadFreeList.end())
        {
            auto id = *iter;
            auto found = std::find_if(threadList.begin(), threadList.end(), [=](std::thread &t) { return (t.get_id() == id); });
            if (found != threadList.end())
            {
                found->join();
                threadList.erase(found);
            }
            iter = threadFreeList.erase(iter);
        }
    } 
});

...

{
    std::lock_guard<std::mutex> lock(threadMutex);
    threadList.push_back(
        std::thread([]() {
            ...
            std::unique_lock<std::mutex> lock(threadFreeMutex);
            threadFreeList.push_back(std::this_thread::get_id());
            threadFreeCV.notify_one();
        })
    );
} 

【讨论】:

  • 您锁定了互斥体,但从未解锁...这是您的代码中的错误还是故意的?
  • @Acidic:故意的。 RAII 语义负责解锁它:“当控制离开创建 lock_guard 对象的范围时, lock_guard 被破坏并释放互斥体。” STL 类基于 RAII 原则构建。
  • 在我的项目中,我在threadList.push_back 语句下方有代码,但仍在范围内。在这种情况下我应该解锁吗?
  • @Acidic: lock_guard 无法手动解锁,只有在超出范围时才能解锁。因此,将push_back 包装在一个新范围内(一对括号)并将lock_guard 放在该范围内,例如:{ ... { lock_guard; push_back; } ... }
  • 这不起作用,因为async 返回一个future 并且这个future 的析构函数阻塞直到removeThread 完成 --> 死锁
【解决方案2】:

当你可以分离它们时,为什么你需要这个线程向量?

// Scope that outlives the threads
boost::barrier out_barrier(N_threads+1);

...

// Starting the threads
for(int i = 0; i < N_threads; i++) {
    std::thread th([&out_barrier]() {
        ...do the job...
        out_barrier.wait();
    });
    th.detach();
}

...

// Wait for threads to finish
out_barrier.wait();

线程不可连接,因此调用析构函数是安全的。 在这种情况下,同步是不可避免的。在我的示例中,它用于连接所有线程,如果您有一个线程向量,则需要同步对向量的访问,因此它们都是一样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多