Basic thread management 21 even happens if the function exits because do_something_in_current thread throws an exception. The destructor of thread guard in listing 2.3 first tests to see if the std:thread object is joinable()1 before calling join()2.This is important,because join() can be called only once for a given thread of execution,so it would therefore be a mis take to do so if the thread had already been joined. The copy constructor and copy-assignment operator are marked =delete 3 to ensure that they're not automatically provided by the compiler.Copying or assigning such an object would be dangerous,because it might then outlive the scope of the thread it was joining.By declaring them as deleted,any attempt to copy a thread guard object will generate a compilation error.See appendix A,section A.2,for more about deleted functions. If you don't need to wait for a thread to finish,you can avoid this exception-safety issue by detachingit.This breaks the association of the thread with the std:thread object and ensures that std:terminate()won't be called when the std:thread object is destroyed,even though the thread is still running in the background. 2.1.4 Running threads in the background Calling detach()on a std:thread object leaves the thread to run in the back- ground,with no direct means of communicating with it.It's no longer possible to wait for that thread to complete;if a thread becomes detached,it isn't possible to obtain a std:thread object that references it,so it can no longer be joined.Detached threads truly run in the background;ownership and control are passed over to the C++Run- time Library,which ensures that the resources associated with the thread are correctly reclaimed when the thread exits. Detached threads are often called daemon threads after the UNIX concept of a daemon process that runs in the background without any explicit user interface.Such threads are typically long-running;they may well run for almost the entire lifetime of the application,performing a background task such as monitoring the filesystem, clearing unused entries out of object caches,or optimizing data structures.At the other extreme,it may make sense to use a detached thread where there's another mechanism for identifying when the thread has completed or where the thread is used for a“fire and forget'”task. As you've already seen in section 2.1.2,you detach a thread by calling the detach ( member function of the std:thread object.After the call completes,the std:thread object is no longer associated with the actual thread of execution and is therefore no longer joinable: std::thread t(do background work); t.detach(); assert(!t.joinable()); In order to detach the thread from a std:thread object,there must be a thread to detach:you can't call detach()on a std:thread object with no associated thread of
Basic thread management 21 even happens if the function exits because do_something_in_current_thread throws an exception. The destructor of thread_guard in listing 2.3 first tests to see if the std::thread object is joinable() B before calling join() c. This is important, because join() can be called only once for a given thread of execution, so it would therefore be a mistake to do so if the thread had already been joined. The copy constructor and copy-assignment operator are marked =delete d to ensure that they’re not automatically provided by the compiler. Copying or assigning such an object would be dangerous, because it might then outlive the scope of the thread it was joining. By declaring them as deleted, any attempt to copy a thread_ guard object will generate a compilation error. See appendix A, section A.2, for more about deleted functions. If you don’t need to wait for a thread to finish, you can avoid this exception-safety issue by detaching it. This breaks the association of the thread with the std::thread object and ensures that std::terminate() won’t be called when the std::thread object is destroyed, even though the thread is still running in the background. 2.1.4 Running threads in the background Calling detach() on a std::thread object leaves the thread to run in the background, with no direct means of communicating with it. It’s no longer possible to wait for that thread to complete; if a thread becomes detached, it isn’t possible to obtain a std::thread object that references it, so it can no longer be joined. Detached threads truly run in the background; ownership and control are passed over to the C++ Runtime Library, which ensures that the resources associated with the thread are correctly reclaimed when the thread exits. Detached threads are often called daemon threads after the UNIX concept of a daemon process that runs in the background without any explicit user interface. Such threads are typically long-running; they may well run for almost the entire lifetime of the application, performing a background task such as monitoring the filesystem, clearing unused entries out of object caches, or optimizing data structures. At the other extreme, it may make sense to use a detached thread where there’s another mechanism for identifying when the thread has completed or where the thread is used for a “fire and forget” task. As you’ve already seen in section 2.1.2, you detach a thread by calling the detach() member function of the std::thread object. After the call completes, the std::thread object is no longer associated with the actual thread of execution and is therefore no longer joinable: std::thread t(do_background_work); t.detach(); assert(!t.joinable()); In order to detach the thread from a std::thread object, there must be a thread to detach: you can’t call detach() on a std::thread object with no associated thread of
22 CHAPTER 2 Managing threads execution.This is exactly the same requirement as for join(),and you can check it in exactly the same way-you can only call t.detach()for a std:thread object t when t.joinable()returns true. Consider an application such as a word processor that can edit multiple docu- ments at once.There are many ways to handle this,both at the UI level and internally. One way that seems to be increasingly common at the moment is to have multiple independent top-level windows,one for each document being edited.Although these windows appear to be completely independent,each with its own menus and so forth, they're running within the same instance of the application.One way to handle this internally is to run each document-editing window in its own thread;each thread runs the same code but with different data relating to the document being edited and the corresponding window properties.Opening a new document therefore requires start- ing a new thread.The thread handling the request isn't going to care about waiting for that other thread to finish,because it's working on an unrelated document,so this makes it a prime candidate for running a detached thread. The following listing shows a simple code outline for this approach. Listing 2.4 Detaching a thread to handle other documents void edit document (std::string const&filename) { open_document_and_display_gui(filename); while(!done editing()) { user_command cmd=get_user_input () if(cmd.type==open_new_document) std::string const new name=get filename from user(); std::thread t(edit_document,new_name); 1 t.detach(); -2 else process_user_input(cmd); } If the user chooses to open a new document,you prompt them for the document to open,start a new thread to open that document 1,and then detach it 2.Because the new thread is doing the same operation as the current thread but on a different file,you can reuse the same function (edit document)with the newly chosen file- name as the supplied argument. This example also shows a case where it's helpful to pass arguments to the function used to start a thread:rather than just passing the name of the function to the std:thread constructor 1,you also pass in the filename parameter.Although other mechanisms could be used to do this,such as using a function object with member
22 CHAPTER 2 Managing threads execution. This is exactly the same requirement as for join(), and you can check it in exactly the same way—you can only call t.detach() for a std::thread object t when t.joinable() returns true. Consider an application such as a word processor that can edit multiple documents at once. There are many ways to handle this, both at the UI level and internally. One way that seems to be increasingly common at the moment is to have multiple independent top-level windows, one for each document being edited. Although these windows appear to be completely independent, each with its own menus and so forth, they’re running within the same instance of the application. One way to handle this internally is to run each document-editing window in its own thread; each thread runs the same code but with different data relating to the document being edited and the corresponding window properties. Opening a new document therefore requires starting a new thread. The thread handling the request isn’t going to care about waiting for that other thread to finish, because it’s working on an unrelated document, so this makes it a prime candidate for running a detached thread. The following listing shows a simple code outline for this approach. void edit_document(std::string const& filename) { open_document_and_display_gui(filename); while(!done_editing()) { user_command cmd=get_user_input(); if(cmd.type==open_new_document) { std::string const new_name=get_filename_from_user(); std::thread t(edit_document,new_name); t.detach(); } else { process_user_input(cmd); } } } If the user chooses to open a new document, you prompt them for the document to open, start a new thread to open that document B, and then detach it c. Because the new thread is doing the same operation as the current thread but on a different file, you can reuse the same function (edit_document) with the newly chosen filename as the supplied argument. This example also shows a case where it’s helpful to pass arguments to the function used to start a thread: rather than just passing the name of the function to the std::thread constructor B, you also pass in the filename parameter. Although other mechanisms could be used to do this, such as using a function object with member Listing 2.4 Detaching a thread to handle other documents b c
Passing arguments to a thread function 23 data instead of an ordinary function with parameters,the Thread Library provides you with an easy way of doing it 2.2 Passing arguments to a thread function As shown in listing 2.4,passing arguments to the callable object or function is funda- mentally as simple as passing additional arguments to the std:thread constructor. But it's important to bear in mind that by default the arguments are copied into inter- nal storage,where they can be accessed by the newly created thread of execution, even if the corresponding parameter in the function is expecting a reference.Here's a simple example: void f(int i,std:string const&s); std:thread t(f,3,"hello"); This creates a new thread of execution associated with t,which calls f(3,"hello"). Note that even though f takes a std:string as the second parameter,the string lit- eral is passed as a char const*and converted to a std:string only in the context of the new thread.This is particularly important when the argument supplied is a pointer to an automatic variable,as follows: void f(int i,std:string const&s); void oops(int some_param) { char buffer[1024]; 4-① sprintf (buffer,"i",some_param); std::thread t(f,3,buffer); 42 t.detach(); In this case,it's the pointer to the local variable buffer1 that's passed through to the new thread 2,and there's a significant chance that the function oops will exit before the buffer has been converted to a std:string on the new thread,thus leading to undefined behavior.The solution is to cast to std:string before passing the buffer to the std::thread constructor: void f(int i,std:string const&s); void not_oops(int some_param) char buffer[1024】; sprintf (buffer,"i",some_param); Using std::string avoids std:thread t(f,3,std::string(buffer)); dangling pointer t.detach(); In this case,the problem is that you were relying on the implicit conversion of the pointer to the buffer into the std:string object expected as a function parameter, because the std:thread constructor copies the supplied values as is,without convert- ing to the expected argument type
Passing arguments to a thread function 23 data instead of an ordinary function with parameters, the Thread Library provides you with an easy way of doing it. 2.2 Passing arguments to a thread function As shown in listing 2.4, passing arguments to the callable object or function is fundamentally as simple as passing additional arguments to the std::thread constructor. But it’s important to bear in mind that by default the arguments are copied into internal storage, where they can be accessed by the newly created thread of execution, even if the corresponding parameter in the function is expecting a reference. Here’s a simple example: void f(int i,std::string const& s); std::thread t(f,3,”hello”); This creates a new thread of execution associated with t, which calls f(3,”hello”). Note that even though f takes a std::string as the second parameter, the string literal is passed as a char const* and converted to a std::string only in the context of the new thread. This is particularly important when the argument supplied is a pointer to an automatic variable, as follows: void f(int i,std::string const& s); void oops(int some_param) { char buffer[1024]; sprintf(buffer, "%i",some_param); std::thread t(f,3,buffer); t.detach(); } In this case, it’s the pointer to the local variable buffer B that’s passed through to the new thread c, and there’s a significant chance that the function oops will exit before the buffer has been converted to a std::string on the new thread, thus leading to undefined behavior. The solution is to cast to std::string before passing the buffer to the std::thread constructor: void f(int i,std::string const& s); void not_oops(int some_param) { char buffer[1024]; sprintf(buffer,"%i",some_param); std::thread t(f,3,std::string(buffer)); t.detach(); } In this case, the problem is that you were relying on the implicit conversion of the pointer to the buffer into the std::string object expected as a function parameter, because the std::thread constructor copies the supplied values as is, without converting to the expected argument type. b c Using std::string avoids dangling pointer
24 CHAPTER 2 Managing threads It's also possible to get the reverse scenario:the object is copied,and what you wanted was a reference.This might happen if the thread is updating a data structure that's passed in by reference,for example: void update_data_for_widget(widget_id w,widget_data&data); 1① void oops_again(widget_id w) { widget datadata; std::thread t(update_data_for_widget,w,data); display_status(); t.join(); process_widget_data(data); 3 } Although update data for widget 1 expects the second parameter to be passed by reference,the std:thread constructor 2 doesn't know that;it's oblivious to the types of the arguments expected by the function and blindly copies the supplied val- ues.When it calls update_data_for_widget,it will end up passing a reference to the internal copy of data and not a reference to data itself.Consequently,when the thread finishes,these updates will be discarded as the internal copies of the supplied arguments are destroyed,and process_widget data will be passed an unchanged data rather than a correctly updated version.For those of you familiar with std:bind,the solution will be readily apparent:you need to wrap the arguments that really need to be references in std::ref.In this case,if you change the thread invoca- tion to std:thread t(update_data_for_widget,w,std::ref(data)); and then update data for widget will be correctly passed a reference to data rather than a reference to a copy of data. If you're familiar with std:bind,the parameter-passing semantics will be unsur- prising,because both the operation of the std:thread constructor and the opera- tion of std::bind are defined in terms of the same mechanism.This means that,for example,you can pass a member function pointer as the function,provided you sup- ply a suitable object pointer as the first argument: class x { public: void do_lengthy_work(); }: x my_xi std:thread t(&X:do_lengthy_work,&my_x); 40 This code will invoke my x.do lengthy work()on the new thread,because the address of my_x is supplied as the object pointer 1.You can also supply arguments to such a member function call:the third argument to the std:thread constructor will be the first argument to the member function and so forth
24 CHAPTER 2 Managing threads It’s also possible to get the reverse scenario: the object is copied, and what you wanted was a reference. This might happen if the thread is updating a data structure that’s passed in by reference, for example: void update_data_for_widget(widget_id w,widget_data& data); void oops_again(widget_id w) { widget_data data; std::thread t(update_data_for_widget,w,data); display_status(); t.join(); process_widget_data(data); } Although update_data_for_widget B expects the second parameter to be passed by reference, the std::thread constructor c doesn’t know that; it’s oblivious to the types of the arguments expected by the function and blindly copies the supplied values. When it calls update_data_for_widget, it will end up passing a reference to the internal copy of data and not a reference to data itself. Consequently, when the thread finishes, these updates will be discarded as the internal copies of the supplied arguments are destroyed, and process_widget_data will be passed an unchanged data d rather than a correctly updated version. For those of you familiar with std::bind, the solution will be readily apparent: you need to wrap the arguments that really need to be references in std::ref. In this case, if you change the thread invocation to std::thread t(update_data_for_widget,w,std::ref(data)); and then update_data_for_widget will be correctly passed a reference to data rather than a reference to a copy of data. If you’re familiar with std::bind, the parameter-passing semantics will be unsurprising, because both the operation of the std::thread constructor and the operation of std::bind are defined in terms of the same mechanism. This means that, for example, you can pass a member function pointer as the function, provided you supply a suitable object pointer as the first argument: class X { public: void do_lengthy_work(); }; X my_x; std::thread t(&X::do_lengthy_work,&my_x); This code will invoke my_x.do_lengthy_work() on the new thread, because the address of my_x is supplied as the object pointer B. You can also supply arguments to such a member function call: the third argument to the std::thread constructor will be the first argument to the member function and so forth. b c d b
Transferring ownership of a thread 25 Another interesting scenario for supplying arguments is where the arguments can't be copied but can only be moved:the data held within one object is transferred over to another,leaving the original object "empty.An example of such a type is std:unique ptr,which provides automatic memory management for dynamically allocated objects.Only one std:unique ptr instance can point to a given object at a time,and when that instance is destroyed,the pointed-to object is deleted.The move constructor and move assignment operator allow the ownership of an object to be trans- ferred around between std:unique_ptr instances(see appendix A,section A.1.1,for more on move semantics).Such a transfer leaves the source object with a NULL pointer.This moving of values allows objects of this type to be accepted as function parameters or returned from functions.Where the source object is a temporary,the move is automatic,but where the source is a named value,the transfer must be requested directly by invoking std::move().The following example shows the use of std:move to transfer ownership of a dynamic object into a thread: void process_big_object(std:unique_ptr<big_object>); std:unique_ptr<big_object>p(new big_object); p->prepare_data(42); std:thread t(process_big_object,std::move(p)); By specifying std:move(p)in the std::thread constructor,the ownership of the big object is transferred first into internal storage for the newly created thread and then into process big object. Several of the classes in the Standard Thread Library exhibit the same ownership semantics as std:unique ptr,and std:thread is one of them.Though std:thread instances don't own a dynamic object in the same way as std:unique ptr does,they do own a resource:each instance is responsible for managing a thread of execution. This ownership can be transferred between instances,because instances of std:thread are movable,even though they aren't copyable.This ensures that only one object is asso- ciated with a particular thread of execution at any one time while allowing program- mers the option of transferring that ownership between objects. 2.3 Transferring ownership of a thread Suppose you want to write a function that creates a thread to run in the background but passes back ownership of the new thread to the calling function rather than wait- ing for it to complete,or maybe you want to do the reverse:create a thread and pass ownership in to some function that should wait for it to complete.In either case,you need to transfer ownership from one place to another. This is where the move support of std::thread comes in.As described in the pre- vious section,many resource-owning types in the C++Standard Library such as std:ifstream and std:unique_ptr are movable but not copyable,and std:thread is one of them.This means that the ownership of a particular thread of execution can be moved between std:thread instances,as in the following example.The example
Transferring ownership of a thread 25 Another interesting scenario for supplying arguments is where the arguments can’t be copied but can only be moved: the data held within one object is transferred over to another, leaving the original object “empty.” An example of such a type is std::unique_ptr, which provides automatic memory management for dynamically allocated objects. Only one std::unique_ptr instance can point to a given object at a time, and when that instance is destroyed, the pointed-to object is deleted. The move constructor and move assignment operator allow the ownership of an object to be transferred around between std::unique_ptr instances (see appendix A, section A.1.1, for more on move semantics). Such a transfer leaves the source object with a NULL pointer. This moving of values allows objects of this type to be accepted as function parameters or returned from functions. Where the source object is a temporary, the move is automatic, but where the source is a named value, the transfer must be requested directly by invoking std::move(). The following example shows the use of std::move to transfer ownership of a dynamic object into a thread: void process_big_object(std::unique_ptr<big_object>); std::unique_ptr<big_object> p(new big_object); p->prepare_data(42); std::thread t(process_big_object,std::move(p)); By specifying std::move(p) in the std::thread constructor, the ownership of the big_object is transferred first into internal storage for the newly created thread and then into process_big_object. Several of the classes in the Standard Thread Library exhibit the same ownership semantics as std::unique_ptr, and std::thread is one of them. Though std::thread instances don’t own a dynamic object in the same way as std::unique_ptr does, they do own a resource: each instance is responsible for managing a thread of execution. This ownership can be transferred between instances, because instances of std::thread are movable, even though they aren’t copyable. This ensures that only one object is associated with a particular thread of execution at any one time while allowing programmers the option of transferring that ownership between objects. 2.3 Transferring ownership of a thread Suppose you want to write a function that creates a thread to run in the background but passes back ownership of the new thread to the calling function rather than waiting for it to complete, or maybe you want to do the reverse: create a thread and pass ownership in to some function that should wait for it to complete. In either case, you need to transfer ownership from one place to another. This is where the move support of std::thread comes in. As described in the previous section, many resource-owning types in the C++ Standard Library such as std::ifstream and std::unique_ptr are movable but not copyable, and std::thread is one of them. This means that the ownership of a particular thread of execution can be moved between std::thread instances, as in the following example. The example