16 CHAPTER 2 Managing threads 2.1 Basic thread management Every C++program has at least one thread,which is started by the C++runtime:the thread running main().Your program can then launch additional threads that have another function as the entry point.These threads then run concurrently with each other and with the initial thread.Just as the program exits when the program returns from main(),when the specified entry point function returns,the thread exits.As you'll see,if you have a std:thread object for a thread,you can wait for it to finish; but first you have to start it,so let's look at launching threads. 2.1.1 Launching a thread As you saw in chapter 1,threads are started by constructing a std::thread object that specifies the task to run on that thread.In the simplest case,that task is just a plain, ordinary void-returning function that takes no parameters.This function runs on its own thread until it returns,and then the thread stops.At the other extreme,the task could be a function object that takes additional parameters and performs a series of independent operations that are specified through some kind of messaging system while it's running,and the thread stops only when it's signaled to do so,again via some kind of messaging system.It doesn't matter what the thread is going to do or where it's launched from,but starting a thread using the C++Thread Library always boils down to constructing a std:thread object: void do_some_work(); std:thread my_thread(do_some_work); This is just about as simple as it gets.Of course,you have to make sure that the <thread>header is included so the compiler can see the definition of the std: thread class.As with much of the C++Standard Library,std:thread works with any callable type,so you can pass an instance of a class with a function call operator to the std::thread constructor instead: class background_task public: void operator()()const do_something () do_something_else(); } }: background task f; std:thread my_thread(f); In this case,the supplied function object is copied into the storage belonging to the newly created thread of execution and invoked from there.It's therefore essential that the copy behave equivalently to the original,or the result may not be what's expected. One thing to consider when passing a function object to the thread constructor is to avoid what is dubbed "C++'s most vexing parse."If you pass a temporary rather
16 CHAPTER 2 Managing threads 2.1 Basic thread management Every C++ program has at least one thread, which is started by the C++ runtime: the thread running main(). Your program can then launch additional threads that have another function as the entry point. These threads then run concurrently with each other and with the initial thread. Just as the program exits when the program returns from main(), when the specified entry point function returns, the thread exits. As you’ll see, if you have a std::thread object for a thread, you can wait for it to finish; but first you have to start it, so let’s look at launching threads. 2.1.1 Launching a thread As you saw in chapter 1, threads are started by constructing a std::thread object that specifies the task to run on that thread. In the simplest case, that task is just a plain, ordinary void-returning function that takes no parameters. This function runs on its own thread until it returns, and then the thread stops. At the other extreme, the task could be a function object that takes additional parameters and performs a series of independent operations that are specified through some kind of messaging system while it’s running, and the thread stops only when it’s signaled to do so, again via some kind of messaging system. It doesn’t matter what the thread is going to do or where it’s launched from, but starting a thread using the C++ Thread Library always boils down to constructing a std::thread object: void do_some_work(); std::thread my_thread(do_some_work); This is just about as simple as it gets. Of course, you have to make sure that the <thread> header is included so the compiler can see the definition of the std:: thread class. As with much of the C++ Standard Library, std::thread works with any callable type, so you can pass an instance of a class with a function call operator to the std::thread constructor instead: class background_task { public: void operator()() const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f); In this case, the supplied function object is copied into the storage belonging to the newly created thread of execution and invoked from there. It’s therefore essential that the copy behave equivalently to the original, or the result may not be what’s expected. One thing to consider when passing a function object to the thread constructor is to avoid what is dubbed “C++’s most vexing parse.” If you pass a temporary rather
Basic thread management 17 than a named variable,then the syntax can be the same as that of a function declara- tion,in which case the compiler interprets it as such,rather than an object definition For example, std:thread my_thread(background_task()); declares a function my thread that takes a single parameter(of type pointer to a func- tion taking no parameters and returning a background task object)and returns a std:thread object,rather than launching a new thread.You can avoid this by nam- ing your function object as shown previously,by using an extra set of parentheses,or by using the new uniform initialization syntax,for example: std:thread my_thread((background_task())); 10 std:thread my_thread{background_task()}; 小2 In the first example 1,the extra parentheses prevent interpretation as a function declaration,thus allowing my thread to be declared as a variable of type std:thread. The second example 2 uses the new uniform initialization syntax with braces rather than parentheses,and thus would also declare a variable. One type of callable object that avoids this problem is a lambda expression.This is a new feature from C++11 which essentially allows you to write a local function,possibly capturing some local variables and avoiding the need of passing additional arguments (see section 2.2).For full details on lambda expressions,see appendix A,section A.5. The previous example can be written using a lambda expression as follows: std:thread my_thread([]( do_something(); do_something_else(); }): Once you've started your thread,you need to explicitly decide whether to wait for it to finish (by joining with it-see section 2.1.2)or leave it to run on its own (by detaching it-see section 2.1.3).If you don't decide before the std:thread object is destroyed, then your program is terminated (the std:thread destructor calls std:terminate()). It's therefore imperative that you ensure that the thread is correctly joined or detached,even in the presence of exceptions.See section 2.1.3 for a technique to han- dle this scenario.Note that you only have to make this decision before the std:thread object is destroyed-the thread itself may well have finished long before you join with it or detach it,and if you detach it,then the thread may continue running long after the std:thread object is destroyed. If you don't wait for your thread to finish,then you need to ensure that the data accessed by the thread is valid until the thread has finished with it.This isn't a new problem-even in single-threaded code it is undefined behavior to access an object after it's been destroyed-but the use of threads provides an additional opportunity to encounter such lifetime issues. One situation in which you can encounter such problems is when the thread function holds pointers or references to local variables and the thread hasn't
Basic thread management 17 than a named variable, then the syntax can be the same as that of a function declaration, in which case the compiler interprets it as such, rather than an object definition. For example, std::thread my_thread(background_task()); declares a function my_thread that takes a single parameter (of type pointer to a function taking no parameters and returning a background_task object) and returns a std::thread object, rather than launching a new thread. You can avoid this by naming your function object as shown previously, by using an extra set of parentheses, or by using the new uniform initialization syntax, for example: std::thread my_thread((background_task())); std::thread my_thread{background_task()}; In the first example B, the extra parentheses prevent interpretation as a function declaration, thus allowing my_thread to be declared as a variable of type std::thread. The second example c uses the new uniform initialization syntax with braces rather than parentheses, and thus would also declare a variable. One type of callable object that avoids this problem is a lambda expression. This is a new feature from C++11 which essentially allows you to write a local function, possibly capturing some local variables and avoiding the need of passing additional arguments (see section 2.2). For full details on lambda expressions, see appendix A, section A.5. The previous example can be written using a lambda expression as follows: std::thread my_thread([]( do_something(); do_something_else(); }); Once you’ve started your thread, you need to explicitly decide whether to wait for it to finish (by joining with it—see section 2.1.2) or leave it to run on its own (by detaching it—see section 2.1.3). If you don’t decide before the std::thread object is destroyed, then your program is terminated (the std::thread destructor calls std::terminate()). It’s therefore imperative that you ensure that the thread is correctly joined or detached, even in the presence of exceptions. See section 2.1.3 for a technique to handle this scenario. Note that you only have to make this decision before the std::thread object is destroyed—the thread itself may well have finished long before you join with it or detach it, and if you detach it, then the thread may continue running long after the std::thread object is destroyed. If you don’t wait for your thread to finish, then you need to ensure that the data accessed by the thread is valid until the thread has finished with it. This isn’t a new problem—even in single-threaded code it is undefined behavior to access an object after it’s been destroyed—but the use of threads provides an additional opportunity to encounter such lifetime issues. One situation in which you can encounter such problems is when the thread function holds pointers or references to local variables and the thread hasn’t b c
18 CHAPTER 2 Managing threads finished when the function exits.The following listing shows an example of just such a scenario. Listing 2.1 A function that returns while a thread still has access to local variables struct func { int&i; func(int&i_):i(i_)(} void operator()() { for(unsigned j=0;j<1000000;++j) Potential access to do_something(i); dangling reference } }: void oops() int some_local_state=0; func my_func(some_local_state); 2 Don't wait std::thread my_thread(my_func); for thread 3 New thread my_thread.detach(); to finish might still } be running In this case,the new thread associated with my thread will probably still be running when oops exits 3,because you've explicitly decided not to wait for it by calling detach()2.If the thread is still running,then the next call to do_something(i)1 will access an already destroyed variable.This is just like normal single-threaded code-allowing a pointer or reference to a local variable to persist beyond the func- tion exit is never a good idea-but it's easier to make the mistake with multithreaded code,because it isn't necessarily immediately apparent that this has happened. One common way to handle this scenario is to make the thread function self- contained and copy the data into the thread rather than sharing the data.If you use a callable object for your thread function,that object is itself copied into the thread,so the original object can be destroyed immediately.But you still need to be wary of objects containing pointers or references,such as that from listing 2.1.In particular, it's a bad idea to create a thread within a function that has access to the local variables in that function,unless the thread is guaranteed to finish before the function exits. Alternatively,you can ensure that the thread has completed execution before the function exits by joining with the thread. 2.1.2 Waiting for a thread to complete If you need to wait for a thread to complete,you can do this by calling join()on the asso- ciated std:thread instance.In the case of listing 2.1,replacing the call to my thread detach (before the closing brace of the function body with a call to my thread.join (
18 CHAPTER 2 Managing threads finished when the function exits. The following listing shows an example of just such a scenario. struct func { int& i; func(int& i_):i(i_){} void operator()() { for(unsigned j=0;j<1000000;++j) { do_something(i); } } }; void oops() { int some_local_state=0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach(); } In this case, the new thread associated with my_thread will probably still be running when oops exits d, because you’ve explicitly decided not to wait for it by calling detach() c. If the thread is still running, then the next call to do_something(i) B will access an already destroyed variable. This is just like normal single-threaded code—allowing a pointer or reference to a local variable to persist beyond the function exit is never a good idea—but it’s easier to make the mistake with multithreaded code, because it isn’t necessarily immediately apparent that this has happened. One common way to handle this scenario is to make the thread function selfcontained and copy the data into the thread rather than sharing the data. If you use a callable object for your thread function, that object is itself copied into the thread, so the original object can be destroyed immediately. But you still need to be wary of objects containing pointers or references, such as that from listing 2.1. In particular, it’s a bad idea to create a thread within a function that has access to the local variables in that function, unless the thread is guaranteed to finish before the function exits. Alternatively, you can ensure that the thread has completed execution before the function exits by joining with the thread. 2.1.2 Waiting for a thread to complete If you need to wait for a thread to complete, you can do this by calling join() on the associated std::thread instance. In the case of listing 2.1, replacing the call to my_thread .detach() before the closing brace of the function body with a call to my_thread.join() Listing 2.1 A function that returns while a thread still has access to local variables Potential access to dangling reference b Don’t wait for thread to finish c New thread might still be running d
Basic thread management 19 would therefore be sufficient to ensure that the thread was finished before the func- tion was exited and thus before the local variables were destroyed.In this case,it would mean there was little point running the function on a separate thread,because the first thread wouldn't be doing anything useful in the meantime,but in real code the original thread would either have work to do itself or it would have launched sev- eral threads to do useful work before waiting for all of them to complete. join()is simple and brute force-either you wait for a thread to finish or you don't.If you need more fine-grained control over waiting for a thread,such as to check whether a thread is finished,or to wait only a certain period of time,then you have to use alternative mechanisms such as condition variables and futures,which we'll look at in chapter 4.The act of calling join()also cleans up any storage associ- ated with the thread,so the std:thread object is no longer associated with the now- finished thread;it isn't associated with any thread.This means that you can call join()only once for a given thread;once you've called join(),the std::thread object is no longer joinable,and joinable()will return false. 2.1.3 Waiting in exceptional circumstances As mentioned earlier,you need to ensure that you've called either join()or detach()before a std:thread object is destroyed.If you're detaching a thread,you can usually call detach()immediately after the thread has been started,so this isn't a problem.But if you're intending to wait for the thread,you need to pick carefully the place in the code where you call join().This means that the call to join (is liable to be skipped if an exception is thrown after the thread has been started but before the call to join () To avoid your application being terminated when an exception is thrown,you therefore need to make a decision on what to do in this case.In general,if you were intending to call join()in the non-exceptional case,you also need to call join()in the presence of an exception to avoid accidental lifetime problems.The next listing shows some simple code that does just that. Listing 2.2 Waiting for a thread to finish struct func; See definition void f() in listing 2.I int some local state=0; func my_func(some_local_state); std::thread t(my_func); try do_something_in_current_thread(); catch(...) t.join();
Basic thread management 19 would therefore be sufficient to ensure that the thread was finished before the function was exited and thus before the local variables were destroyed. In this case, it would mean there was little point running the function on a separate thread, because the first thread wouldn’t be doing anything useful in the meantime, but in real code the original thread would either have work to do itself or it would have launched several threads to do useful work before waiting for all of them to complete. join() is simple and brute force—either you wait for a thread to finish or you don’t. If you need more fine-grained control over waiting for a thread, such as to check whether a thread is finished, or to wait only a certain period of time, then you have to use alternative mechanisms such as condition variables and futures, which we’ll look at in chapter 4. The act of calling join() also cleans up any storage associated with the thread, so the std::thread object is no longer associated with the nowfinished thread; it isn’t associated with any thread. This means that you can call join() only once for a given thread; once you’ve called join(), the std::thread object is no longer joinable, and joinable() will return false. 2.1.3 Waiting in exceptional circumstances As mentioned earlier, you need to ensure that you’ve called either join() or detach() before a std::thread object is destroyed. If you’re detaching a thread, you can usually call detach() immediately after the thread has been started, so this isn’t a problem. But if you’re intending to wait for the thread, you need to pick carefully the place in the code where you call join(). This means that the call to join() is liable to be skipped if an exception is thrown after the thread has been started but before the call to join(). To avoid your application being terminated when an exception is thrown, you therefore need to make a decision on what to do in this case. In general, if you were intending to call join() in the non-exceptional case, you also need to call join() in the presence of an exception to avoid accidental lifetime problems. The next listing shows some simple code that does just that. struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); try { do_something_in_current_thread(); } catch(...) { t.join(); Listing 2.2 Waiting for a thread to finish See definition in listing 2.1 b
20 CHAPTER 2 Managing threads throw; t.join(); 42 } The code in listing 2.2 uses a try/catch block to ensure that a thread with access to local state is finished before the function exits,whether the function exits normally 2 or by an exception 1.The use of try/catch blocks is verbose,and it's easy to get the scope slightly wrong,so this isn't an ideal scenario.If it's important to ensure that the thread must complete before the function exits-whether because it has a refer- ence to other local variables or for any other reason-then it's important to ensure this is the case for all possible exit paths,whether normal or exceptional,and it's desirable to provide a simple,concise mechanism for doing so. One way of doing this is to use the standard Resource Acquisition Is Initialization (RAll)idiom and provide a class that does the join()in its destructor,as in the follow- ing listing.See how it simplifies the function f(). Listing 2.3 Using RAll to wait for a thread to complete class thread_guard std::thread&t; public: explicit thread guard(std::thread&t_): t(E_) 0 thread_guard() if(t.joinable()) t.join(); } thread guard(thread guard const&)=delete; 13 thread_guard&operator=(thread_guard const&)=delete; }: struct func; See definition void f() in listing 2.1 { int some_local_state=0; func my_func(some_local_state); std:thread t(my_func); thread_guard g(t); do_something_in_current_thread(); } 1④ When the execution of the current thread reaches the end of f,the local objects are destroyed in reverse order of construction.Consequently,the thread guard object g is destroyed first,and the thread is joined with in the destructor 2.This
20 CHAPTER 2 Managing threads throw; } t.join(); } The code in listing 2.2 uses a try/catch block to ensure that a thread with access to local state is finished before the function exits, whether the function exits normally c or by an exception B. The use of try/catch blocks is verbose, and it’s easy to get the scope slightly wrong, so this isn’t an ideal scenario. If it’s important to ensure that the thread must complete before the function exits—whether because it has a reference to other local variables or for any other reason—then it’s important to ensure this is the case for all possible exit paths, whether normal or exceptional, and it’s desirable to provide a simple, concise mechanism for doing so. One way of doing this is to use the standard Resource Acquisition Is Initialization (RAII) idiom and provide a class that does the join() in its destructor, as in the following listing. See how it simplifies the function f(). class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_): t(t_) {} ~thread_guard() { if(t.joinable()) { t.join(); } } thread_guard(thread_guard const&)=delete; thread_guard& operator=(thread_guard const&)=delete; }; struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); thread_guard g(t); do_something_in_current_thread(); } When the execution of the current thread reaches the end of f e, the local objects are destroyed in reverse order of construction. Consequently, the thread_guard object g is destroyed first, and the thread is joined with in the destructor c. This Listing 2.3 Using RAII to wait for a thread to complete c b c d See definition in listing 2.1 e