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:
Engine
class that handles input and output to the worker thread.Engine
creation, open your worker thread, andEngine
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.