Execute lambda with CreateThread

Viewed 133

Is there a better way to use CreateThread than creating a free function each time for the sole purpose of casting lpParameter?

Are there any modern alternatives to CreateThread for creating persistent threads?

Edit: Perhaps you should just use std::async(lambda). I imagine that it's just implemented with CreateThread. Maybe the answer to this question is looking up how std::async is implemented (assuming it's a library feature).

DWORD WINAPI MyThreadFunction(
  _In_ LPVOID lpParameter
)
{
    ((MyClass*)lpParameter)->RunLoop();
}

void MyClass::LaunchThread()
{
    CreateThread( 
            NULL,                   // default security attributes
            0,                      // use default stack size  
            MyThreadFunction,       // thread function name
            this,          // argument to thread function 
            0,                      // use default creation flags 
            NULL);   // returns the thread identifier 
}
3 Answers

There are several mechanisms for achieving parallelism (std::async etc. as mentioned above).

But the modern one which is most similar to your original code with CreateThread is std::thread. It can be constructed with a global function, a lambda, or a class method (which seems the best fit for you):

m_thread = std::thread([this](){ RunLoop(); });  // pass a lambda

or

m_thread = std::thread(&MyClass::RunLoop, this); // pass a method

Note that a std::thread starts to run (potentially) when constructed. Also note that, std::async does not guarantee that it will run on a separate thread and even if it does run on a thread, it could be a thread from a pool. The behaviour might not be the same as with your original CreateThread.

Here's a complete example of using std::thread (including cancellation):

#include <thread>
#include <chrono>
#include <atomic>
#include <iostream>

class MyClass
{
public:
    MyClass()  {}
    ~MyClass() { EndThread(); }

    void LaunchThread()
    {
        EndThread();    // in case it was already running
        m_bThreadShouldExit = false;
        // Start the thread with a class method:
        m_thread = std::thread(&MyClass::RunLoop, this);
    }

    void EndThread()
    {
        // Singal the thread to exit, and wait for it:
        m_bThreadShouldExit = true;
        if (m_thread.joinable())
        {
            m_thread.join();
        }
    }

    void RunLoop()
    {
        std::cout << "RunLoop started" << std::endl;
        while (!m_bThreadShouldExit)
        {
            std::cout << "RunLoop doing something ..." << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
        std::cout << "RunLoop ended" << std::endl;
    }

private:
    std::thread         m_thread;
    std::atomic_bool    m_bThreadShouldExit{ false };
};


int main()
{
    MyClass m;
    m.LaunchThread();
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    m.EndThread();
}

Possible output:

RunLoop started
RunLoop doing something ...
RunLoop doing something ...
RunLoop doing something ...
RunLoop doing something ...
RunLoop doing something ...
RunLoop ended

I looked in to MSVC implementation of std::async and they implemented it using ::Concurrency::create_task which straight forwardly accepts a callable object.

https://docs.microsoft.com/en-us/cpp/parallel/concrt/task-parallelism-concurrency-runtime

I also looked into their implementation of create_task

template<typename _Ty>
__declspec(noinline) // Ask for no inlining so that the _CAPTURE_CALLSTACK gives us the expected result
explicit task(_Ty _Param)
{
    task_options _TaskOptions;
    details::_ValidateTaskConstructorArgs<_ReturnType,_Ty>(_Param);

    _CreateImpl(_TaskOptions.get_cancellation_token()._GetImplValue(), _TaskOptions.get_scheduler());
    // Do not move the next line out of this function. It is important that _CAPTURE_CALLSTACK() evaluates to the call site of the task constructor.
    _SetTaskCreationCallstack(_CAPTURE_CALLSTACK());

    _TaskInitMaybeFunctor(_Param, decltype(details::_IsCallable(_Param,0))());
}

and so it turns out that launching a lambda on a new thread is quite difficult and beyond the scope of this question.

std::async() and std::thread(, <args...>) are most likely internally implemented as you just did, the only exception is that lambdas without captures can be implicitly converted to function pointers, which pretty much can be passed straight away to CreateThread function with nullptr lpParameter. Lambdas with capture list are pretty much syntactic sugar but internally they translate to sth like this (very simplified):

struct <internal_lambda_name>
{
    <capture list...> fields...;

    void operator()(<arguments...>){<code...>;}
};

So they pretty much translate to objects of struct type thus they need some way to store all those captures and in order to be executed on other thread with CreateThread function they need some way of ensuring that the capture list data stored in them will be available during their execution.

Related