4

Microsoft's PPL library contains powerful parallelisation concepts, and implements them using a thread pool, so no new threads are normally created when running PPL tasks. However, there doesn't seem to be a way to explicitly stop the threads in the thread pool.

The reason why I want to explicitly stop the threads is because of Qt. Some Qt methods store information in an allocated class instance, and a pointer to this class instance is stored in thread-local storage. This memory is only cleaned up if threads are stopped in a graceful way. If not, Qt cannot clean up this allocated memory.

Combining PPL with Qt implies that this memory is not decently deallocated at exit, which is not a problem in itself, but unfortunately this non-deallocated memory is reported as a memory leak by our memory allocation library (see Is anyone using valgrind and Qt? for a similar problem).

We noticed that if we create threads ourself (so not using the PPL thread pool), no leaks are reported. If we use PPL, leaks are reported.

So, the question: is there a way to explicitly stop the threads in the PPL thread pool?

Chronial
  • 55,303
  • 13
  • 76
  • 85
Patrick
  • 22,097
  • 9
  • 57
  • 125
  • Have you tried to change the scheduler (using different policies maybe)? https://docs.microsoft.com/en-us/cpp/parallel/concrt/scheduler-policies?view=vs-2019 maybe the pool will be destroyed when the scheduler is detached. PPL seems to be using its own pool since VS 2015: https://docs.microsoft.com/en-us/cpp/parallel/concrt/task-scheduler-concurrency-runtime?view=vs-2019 Otherwise, you can't stop any thread from outside of it. You usually ask the thread nicely to stop (using some event, etc.). There is a TerminateThread API, but it's usually not the recommended to use it (not recommended). – Simon Mourier Jul 03 '20 at 11:53
  • 1
    It does not seem PPL to close any resources. I tried to track the life-time of the `Concurrency::details::ThreadProxy` object (that holds handle to Windows thread) and I do not see that destructor of this class is being called. After you exit from the `main` function, OS just kills all the threads without cleanup. Code of the PPL library is not of the best quality. There are no smart pointers used there. – Dmytro Ovdiienko Jul 09 '20 at 11:00

1 Answers1

-1

The PPL follows very much the same concept in C++ like async programming in C#.

The general idea is "cooperative cancelation" - you can ask a thread to stop, the thread decides when it is possible to cancel. You should not kill tasks/threads so that a thread is not stopped at an undefined instruction.

Here you can see a sample how to stop a thread / task with 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;
}

But to help you more - can you provide us some source code?

Bernd
  • 1,589
  • 6
  • 16
  • Thanks for your answer. Unfortunately, this does not address the core issue of the situation – thread local variables. Your example does not actually cause the thread to stop. The thread will be killed by the process shutdown and will never destruct its thread local variables. If you want to see that in code, declare a class that outputs a message in its destructor, declare a thread-local instance of that class in do_work and see that the message never gets displayed. Try running this godbolt in msvc: https://godbolt.org/z/DLecau – Chronial Jul 06 '20 at 09:07
  • MSVC seems to be currently not available at godbolt. You need to wait for the thread to complete. Local variable destruction is done at the very end. This "auto-wait-feature" is integrated in std::future. See cppreference: https://en.cppreference.com/w/cpp/thread/future/%7Efuture and https://en.cppreference.com/w/cpp/thread/async (Notes) In the PPL you have to wait explicitly. – Bernd Jul 06 '20 at 09:42
  • By the way, you should not call get() - otherwise it is just overhead... – Bernd Jul 06 '20 at 09:45
  • Here you can see a sample: https://godbolt.org/z/3sysok – Bernd Jul 06 '20 at 10:02
  • You should avoid using PPL directly - just use std::async and std::future if you can. In MSVC it is implemented via PPL under the hood. – Bernd Jul 06 '20 at 10:08
  • I am aware that msvc is not available at godbolt – you will have to try that locally. Your code will not output "destoying C" if you actually run it under msvc. That is the problem that needs solving. Here's an online link: https://rextester.com/UEJ92122 – Chronial Jul 06 '20 at 13:24
  • 1
    Thanks, for clarity. Now I got your point. You don't use the PPL directly - but you are unhappy with the MS implementation for std::async. See this link: https://stackoverflow.com/questions/50897768/in-visual-studio-thread-local-variables-destructor-not-called-when-used-with This seems to be a well known bug. I'll check if there is a workaround. BTW: MSVC is availble at godbolt - but it shows sometimes things like "Bad Gateway" – Bernd Jul 06 '20 at 14:09
  • 1
    It would be more clear with that link: https://github.com/microsoft/STL/issues/949. – Bernd Jul 06 '20 at 14:21
  • msvc is only available at godbolt for compiling. You can not run the result. I am well aware of the non-conformance of the msvc STL. This question is about a way to shut down the default process pool. I don't think that's possible, but maybe I am missing something. – Chronial Jul 06 '20 at 15:26
  • Yes, I think that you need to go very deep in the STL (I found some pieces but it has some conditions) ... So, it seems to be not worth (only to make a pull request) - so for debugging you should use jthread or remove those pieces that need thread_local – Bernd Jul 06 '20 at 15:56
  • @BerndBaumanns, thanks for the link. That's indeed the problem. PPL doesn't correctly stop threads, but just let the OS kill them at exit. I would indeed get rid of thread_local if it would be my code, but some of the code underneath is Qt, and I don't want to deviate from the standard Qt code. I probably will have to live with this problem. – Patrick Jul 13 '20 at 07:44