【问题标题】:Terminate threads in the PPL thread pool终止 PPL 线程池中的线程
【发布时间】:2019-06-25 08:08:06
【问题描述】:

Microsoft 的 PPL 库包含强大的并行化概念,并使用线程池实现它们,因此在运行 PPL 任务时通常不会创建新线程。但是,似乎没有办法显式停止线程池中的线程。

我想显式停止线程的原因是因为 Qt。一些 Qt 方法将信息存储在分配的类实例中,指向该类实例的指针存储在线程本地存储中。只有当线程以优雅的方式停止时,才会清理此内存。如果没有,Qt 就不能清理这个分配的内存。

将 PPL 与 Qt 结合意味着该内存在退出时没有适当地释放,这本身不是问题,但不幸的是,我们的内存分配库将这种未释放的内存报告为内存泄漏(请参阅Is anyone using valgrind and Qt? for类似的问题)。

我们注意到,如果我们自己创建线程(因此不使用 PPL 线程池),则不会报告泄漏。如果我们使用 PPL,则会报告泄漏。

那么,问题来了:有没有办法显式停止 PPL 线程池中的线程?

【问题讨论】:

  • 您是否尝试过更改调度程序(可能使用不同的策略)? docs.microsoft.com/en-us/cpp/parallel/concrt/… 可能会在调度程序分离时销毁池。自 VS 2015 以来,PPL 似乎一直在使用自己的池:docs.microsoft.com/en-us/cpp/parallel/concrt/… 否则,您无法从其外部停止任何线程。您通常会很好地要求线程停止(使用某些事件等)。有一个 TerminateThread API,但通常不推荐使用它(不推荐)。
  • PPL 似乎没有关闭任何资源。我试图跟踪 Concurrency::details::ThreadProxy 对象(持有 Windows 线程句柄)的生命周期,但我没有看到正在调用此类的析构函数。从main 函数退出后,操作系统会直接杀死所有线程而不进行清理。 PPL 库的代码质量不是最好的。那里没有使用智能指针。

标签: c++ windows winapi threadpool ppl


【解决方案1】:

PPL 在 C++ 中遵循与 C# 中的异步编程非常相似的概念。

一般的想法是“合作取消”——你可以要求一个线程停止,线程决定什么时候可以取消。 您不应终止任务/线程,以免线程不会在未定义的指令处停止。

在这里您可以看到如何使用 ppl 停止线程/任务的示例:

include <ppltasks.h>
#include <concrt.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

bool do_work()
{
    // Simulate work.
    wcout << L"Performing work..." << endl;
    wait(250);
    return true;
}

int wmain()
{
    cancellation_token_source cts;
    auto token = cts.get_token(); // <-- allow early cancellation, therefore share a cancellation_token

    wcout << L"Creating task..." << endl;

    // Create a task that performs work until it is canceled.
    auto t = create_task([&]
    {
        bool moreToDo = true;
        while (moreToDo)
        {
            // Check for cancellation.
            if (token.is_canceled()) //<-- check for cancellation in the background thread
            {
                // Cancel the current task.
                cancel_current_task(); //<-- informs the caller, "hey I got it..."
            }
            else 
            {
                // Perform work.
                moreToDo = do_work();
            }
        }
    }, token);

    // Wait for one second and then cancel the task.
    wait(1000);

    wcout << L"Canceling task..." << endl;
    cts.cancel();

    // Wait for the task to cancel.
    wcout << L"Waiting for task to complete..." << endl;

    t.wait(); //<-- this is import

    wcout << L"Done." << endl;
}

但是为了帮助您更多 - 您可以提供一些源代码吗?

【讨论】:

  • 感谢您的回答。不幸的是,这并没有解决这种情况的核心问题——线程局部变量。您的示例实际上并没有导致线程停止。该线程将被进程关闭杀死,并且永远不会破坏其线程局部变量。如果您想在代码中看到这一点,请在其析构函数中声明一个输出消息的类,在 do_work 中声明该类的线程本地实例,然后查看该消息永远不会显示。尝试在 msvc 中运行这个 Godbolt:godbolt.org/z/DLecau
  • MSVC 目前似乎在 Godbolt 上不可用。您需要等待线程完成。局部变量销毁在最后完成。这个“自动等待功能”集成在 std::future 中。请参阅 cppreference:en.cppreference.com/w/cpp/thread/future/%7Efutureen.cppreference.com/w/cpp/thread/async(注释)在 PPL 中,您必须明确等待。
  • 顺便说一句,你不应该调用 get() - 否则它只是开销......
  • 感谢您的澄清。现在我明白你的意思了。您不直接使用 PPL - 但您对 std::async 的 MS 实现不满意。请参阅此链接:stackoverflow.com/questions/50897768/… 这似乎是一个众所周知的错误。我会检查是否有解决方法。顺便说一句:MSVC 在 Godbolt 上可用 - 但它有时会显示“坏网关”之类的东西
  • 使用该链接会更清楚:github.com/microsoft/STL/issues/949
猜你喜欢
  • 2023-03-18
  • 2023-02-20
  • 2020-11-13
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多