简体   繁体   中英

c++ std::thread join() after thread is out of scope

my first attempt at a real question in here. I've searched around, but haven't gotten a good answer to come up, so apologies if this is a LMGTFY question.

I am working on a runtime loop for a c++ application engine. It's a standard run loop, that I want to execute on it's own thread, so I can control the execution timing without relying on OS specific dependencies. However, I want to still have the app window play nice with os, in this case Mac OSX, so I have an objective-C platform object that passes key/mouse input to my application. My intent is to have the engine collect input through atomic arrays, and then the app can appropriately respond to Cocoa callbacks from the OS, but I can still retain control over the game loop.

The problem I'm running into is that my current setup involves a method for starting the thread, and a separate thread for joining the thread so Darwin can be happy with how the threads all terminate.

//This is all in a class named engine.
id thread; // What type?
bool engineRunning;

int Engine::startYourEngines() {
        engineRunning = true;
        std::thread thread (engineLoop()); //How do I keep this thread?
        
    }

    int Engine::stopYourEngines() {
        engineRunning = false;
        thread.join(); //Thread is out of scope...or is it?
        return 0;
    }
    
    int Engine::engineLoop() { //Only invoke this with Engine::startYourEngines()
        engineThread = std::this_thread::get_id();
        
        do {
            // do things here!
            }
        } while (engineRunning);
        
        return 0;
    } /* Engine::engineLoop() */

All of the examples I find use a single function for launching the thread and joining it. I don't want to detach the thread because that's just a can of worms. It seems like getting the thread ID is a way to identify the thread, but then I don't know how to use the ID to find the thread again. That seems to be the most straightforward way to do what I want to do.

It seems like you want to have an object Engine launch a worker thread , and hold the reference to that context so it can be neatly closed when its time to shutdown. If you're working with C++20 you'd use jthread s and stop_token s for super neat RAII. If you're not using 20 (which is likely), then you have to roll your own.

I will call this the stop token idiom since that's what the C++20 concept is called. (I assume) you want to do the following:

  • Create a neat Engine class that handles input and output to the worker thread.
  • On Engine creation, open your worker thread, and
  • keep it running until Engine destruction.

If you must use an init-type method ( startYourEngines ) and a teardown-type method ( stopYourEngines ), then a possible solution would look like this.

// each start requires a paired stop
// may not double-start or double-stop
// starting and stopping are not thread-safe (may not run at the same time in different threads)
class Engine {
public:
    Engine() : run_engine_{false}, worker_{} {}

    void startYourEngines() {
        run_engine_ = true; // tell the engine it's okay to run
        worker_ = std::thread([&]{ engineLoop(); }); // start running the engine
    }

    void stopYourEngines() { // changed return type to void since you're not returning anything
        run_engine_ = false; // tell the engine to stop running
        worker_.join(); // wait for engine to stop running
    }

    void engineLoop() { // changed return type to void since you're not returning anything
        while (run_engine_) {
            // do things here!
        }
    }
};

In the above, each startYourEngines call must be paired with a stopYourEngines call. You may not double-start or double stop your engines, and you may not call startYourEngines and stopYourEngines concurrently. There are ways to allow start and stop calls from different threads, but it's easier to just forbid the developer from doing this.

I personally find the "init" and "teardown" style functions odious. Since your code ends up looking like

Engine engine;
engine.startYourEngines();
...do program stuff here until ready to shutdown...
engine.stopYourEngines();

A neater alternative is to incorporate RAII-style code, where constructing an Engine creates the thread of execution, and deconstructing an Engine tears it down. But the above should be enough to get you going.

After a day of hacking around, here's what I came up with as a workable solution.

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

class Engine {
public:
    bool engineRunning = true;
    void engineLoop() {
        do {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            std::cout << "engine running\n";
        } while (engineRunning);
    }
    std::thread engineThread;
    Engine() {
        engineThread = std::thread(&Engine::engineLoop, this);
    }
    ~Engine() {
        engineRunning = false;
        if (engineThread.joinable()) engineThread.join();
    }
};

int main(int argc, const char * argv[]) {
    std::cout << "start program\n";
    Engine *testThread = new Engine;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "kill engine \n";
    testThread->engineRunning = false;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Done!\n";
    return 0;
}

Thank you for the comments and help! Feel free to comment on the code above! I always want to get better!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM