How can I easily create several threads in C++?
For testing or even applications which need to run a bunch of things at the same time, kicking off multiple threads at the same time is really nice.
For instance, if you are building a logging mechanism, you may want to make sure it is thread safe so that messages don’t end up garbled. We could test that our logger is working by sending it messages from 10 threads at the same time.
//*.cxx #include <iostream> #include <mutex> #include <string> #include <thread> #include <vector> namespace { std::mutex g_msgLock; } void info(const char * msg) { std::unique_lock<std::mutex> lock(g_msgLock); std::cout << msg << '\n'; // don't flush } int main(int argc, char** argv) { info("Start message.."); std::vector<std::thread> threads; unsigned int threadCount = 10; threads.reserve(threadCount); for (unsigned int i = 0; i < threadCount; i++) { // Here we start the threads using lambdas threads.push_back(std::thread([&, i](){ std::string msg = std::string("THREADED_TEST_INFO_MESSAGE: ") + std::to_string(i); info(msg.c_str()); })); } for(auto& thread : threads){ thread.join(); } info("End message.."); }
Now a couple of things to note.
If I would’ve used the reference capture [&], then I could be referencing the i variable at a time where it was being updated by another thread, so we use the capture by value [=] here so that we get the value of i at the time the thread was started. If we used the reference, then we could see output like:
THREADED_TEST_INFO_MESSAGE: 5 THREADED_TEST_INFO_MESSAGE: 4 THREADED_TEST_INFO_MESSAGE: 4 THREADED_TEST_INFO_MESSAGE: 3 THREADED_TEST_INFO_MESSAGE: 8 THREADED_TEST_INFO_MESSAGE: 6 THREADED_TEST_INFO_MESSAGE: 5 THREADED_TEST_INFO_MESSAGE: 9 THREADED_TEST_INFO_MESSAGE: 10
Also, every thread is started when we push the thread back into the vector. This means that the threads could actually finish work before we get to the next loop, which would mean that we never really stressed the logger with a bunch of writes at the same time. However, you can see from output like this that we are stressing it:
THREADED_TEST_INFO_MESSAGE: 3 THREADED_TEST_INFO_MESSAGE: 2 THREADED_TEST_INFO_MESSAGE: 1 THREADED_TEST_INFO_MESSAGE: 0 THREADED_TEST_INFO_MESSAGE: 4 THREADED_TEST_INFO_MESSAGE: 6 THREADED_TEST_INFO_MESSAGE: 5 THREADED_TEST_INFO_MESSAGE: 7 THREADED_TEST_INFO_MESSAGE: 8 THREADED_TEST_INFO_MESSAGE: 9
Let’s say that we weren’t sure that we were hitting the logger at the same time and wanted to ensure that. We can use C++14’s feature of shared_mutex to control this nicely:
//*.cxx #include <iostream> #include <mutex> #include <shared_mutex> // requires c++14 #include <string> #include <thread> #include <vector> namespace { std::mutex g_msgLock; std::shared_timed_mutex g_testingLock; } void info(const char * msg) { std::unique_lock<std::mutex> lock(g_msgLock); std::cout << msg << '\n'; // don't flush } int main(int argc, char** argv) { info("Start message.."); std::vector<std::thread> threads; unsigned int threadCount = 10; threads.reserve(threadCount); { // Scope for locking all threads std::lock_guard<std::shared_timed_mutex> lockAllThreads(g_testingLock); // RAII (scoped) lock for (unsigned int i = 0; i < threadCount; i++) { // Here we start the threads using lambdas threads.push_back(std::thread([&, i](){ // Here we block and wait on lockAllThreads std::shared_lock<std::shared_timed_mutex> threadLock(g_testingLock); std::string msg = std::string("THREADED_TEST_INFO_MESSAGE: ") + std::to_string(i); info(msg.c_str()); })); } } // End of scope, lock is released, all threads continue now for(auto& thread : threads){ thread.join(); } info("End message.."); }
Categories