1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
Multi threading in bf(C++) starts off with objects of the class
hi(thread)tt(std::thread). Each object of this class handles a separate
thread.
Before using tt(Thread) objects the tthi(thread) header file must be included.
Thread objects can be constructed in various ways:
itemization(
ittq(thread() noexcept)
(The default constructor creates a tt(thread) object. As it receives no
function to execute, it does not start a separate thread of execution. It is
used, e.g., as a data member of a class, allowing class objects to start a
separate thread at some later point in time;)
ittq(thread(thread &&tmp) noexcept)
(The move constructor takes ownership of the thread controlled by
tt(tmp), while tt(tmp), if it runs a thread, loses control over its
thread. Following this, tt(tmp) is in its default state, and the newly created
thread is responsible for calling, e.g., tt(join).)
ittq(explicit thread(Fun &&fun, Args &&...args))
(This em(member template) (cf. section ref(MEMTEMP)) expects a function
(or functor) as its first argument. The function is immediately started as a
separate thread. If the function (or functor) expects arguments, then these
arguments can be passed to the tt(thread's) constructor immediately following
its first (function) argument. Additional arguments are passed with their
proper types and values to tt(fun). Following the tt(thread) object's
construction, a separately running thread of execution is started.
The notation tt(Arg &&...args) indicates that any additional arguments are
passed as is to the function. The types of the arguments that are passed to
the tt(thread) constructor and that are expected by the called function must
match: values must be values, references must be reference, r-value references
must be r-value references (or move construction must be supported). The
following example illustrates this requirement:
verbinclude(-ans4 examples/threadargs.cc)
itemization(
it() At lines 43 through 45 we see a value, reference, and and r-value
reference being passed to a tt(std::thread): with
the functions running the threads expecting matching argument types.
it() Line 47 fails to compile, as a value argument doesn't match the
reference expected by tt(refArg). Note that this problem was solved in line 43
by using the tt(std::ref) function.
it() On the other hand lines 49 and 58 compile OK, as tt(int) values and
class-types supporting move operations can be passed as values to functions
expecting r-value references. In this case notice that the functions expecting
the r-value references do not access the provided arguments (except for the
actions performed by their move constructors), but use move construction to
create temporary values or objects on which the functions operate.
it() Lines 52 and 55 won't compile as the tt(NoMove) struct doesn't offer
a move constructor.
)
Be careful when passing local variables as arguments to thread objects: if
the thread continues to run when the function whose local variables are used
terminates, then the thread suddenly uses wild pointers or wild references, as
the local variables no longer exist. To prevent this from happening
(illustrated by the next example) do as follows:
itemization(
it() pass an anonymous copy of the local variable as argument to the
tt(thread) constructor, or
it() call tt(join) on the thread object to ensure that the thread has
finished within the local variable's lifetime.
)
verbinclude(-ans4 examples/locals.cc)
In line 18 be sure not to call tt(std::ref(text)) instead of
tt(std::string(text)).
If the thread cannot be created a tt(std::system_error) exception is
thrown.
Since this constructor not only accepts functions but also function objects as
its first argument, a emi(local context) may be passed to the function
object's constructor. Here is an example of a thread receiving a function
object using a local context:
verbinclude(-a examples/functorthread.cc)
)
)
The class tt(std::thread) does not provide a copy constructor.
The following members are available:
itemization(
itt(thread &operator=(thread &&tmp) noexcept):
quote(If the operator's left-hand side operand (lhs) is a joinable
thread, then tt(terminate) is called. Otherwise, tt(tmp) is assigned to the
operator's lhs and tt(tmp's) state is changed to the thread's default state
(i.e., tt(thread())).)
ithtq(detach)(void detach())
(Requires tt(joinable) (see below) to return tt(true). The thread for
which tt(detach) is called continues to run. The (e.g., parent) thread calling
tt(detach) continues immediately beyond the tt(detach)-call. After calling
tt(object.detach()), `tt(object)' no longer represents the (possibly still
continuing but now detached) thread of execution. It is the detached thread's
implementation's responsibility to release its resources when its execution
ends.
Since tt(detach) disconnects a thread from the running program, e.g.,
tt(main) no longer can wait for the thread's completion. As a program
ends when tt(main) ends, its still running detached threads also stop, and
a program may not properly finish all its threads, as demonstrated by
the following example:
verbinclude(-a examples/threads2.cc)
A detached thread may very well continue to run after the function that
launched it has finished. Here, too, you should be very careful not to pass
local variables to the detached thread, as their references or pointers will
be undefined once the function defining the local variables terminates:
verbinclude(-a examples/detached.cc)
)
ithtq(get_id)(id get_id() const noexcept)
(If the current object does not represent a running thread
hi(id)tt(thread::id()) is returned. Otherwise, the thread's unique ID
(also obtainable from with the thread from tt(this_thread::get_id())) is
returned.)
ithtq(join)(void join())
(Requires tt(joinable) to return tt(true). Blocks the thread calling
tt(join) until the thread for which tt(join) is called has
completed. Following its completion the object whose tt(join) member was
called no longer represents a running thread, and its tt(get_id) member will
return tt(std::thread::id()).
This member was used in several examples shown so far. As noted: when
tt(main) ends while a joinable thread is still running, tt(terminate) is
called, aborting the program.)
ithtq(joinable)(bool joinable() const noexcept)
(returns tt(object.get_id() != id()), where tt(object) is the
tt(thread) object for which tt(joinable) was called.)
ithtq(swap)(void swap(thread &other) noexcept)
(The states of the tt(thread) object for which tt(swap) was called and
tt(other) are swapped. Note that threads may always be swapped, even when
their thread functions are currently being executed.)
ithtq(hardware_concurrency)
(unsigned thread::hardware_concurrency() noexecpt)
(This static member returns the number of threads that can run at the
same time on the current computer. On a stand-alone multi-core computer it
(probably) returns the number of cores.)
)
Things to note:
itemization(
it() When intending to define an anonymous thread it may appear not to
start, unless you immediately also call tt(join). E.g.,
verb(
void doSomething();
int main()
{
thread(doSomething); // nothing happens??
thread(doSomething).join() // doSomething is executed??
}
)
This similar to the situation we encountered in section ref(UNIFORMINIT):
the first statement doesn't define an anonymous tt(thread) object at all. It
simply defines the tt(thread) object tt(doSomething). Consequently,
compilation of the second statement fails, as there is no tt(thread(thread &))
constructor. When the first statement is omitted, the tt(doSomething) function
is executed by the second statement. If the second statement is omitted, a
default constructed tt(thread) object by the name of tt(doSomething) is
defined.
it() A thread only starts after its construction has completed. This
includes move constructions or move assignments. E.g., in a statement like
verb(
thread object(thread(doSomething));
)
the move constructor is used to transfer control from an anonymous thread
executing tt(doSomething) to the thread tt(object). Only after tt(object)'s
construction has completed tt(doSomething) is started in the separate thread.
it() Exceptions thrown from the thread (e.g., by the function defining the
thread's actions) are local to the executed thread. Either they must be caught
by the executing thread (as each running thread has its own execution stack),
or they can be passed to the starting thread using a tt(packaged_task) and a
tt(future) (cf., respectively, sections ref(PACKAGE) and ref(FUTURE)).
)
A thread ends when the function executing a thread finishes. When a
tt(thread) object is destroyed while its thread function is still running,
tt(terminate) is called, aborting the program's end. Bad news: the destructors
of existing objects aren't called and exceptions that are thrown are left
uncaught. This happens in the following program as the thread is still active
when tt(main) ends:
verbinclude(-a examples/terminate.cc)
There are several ways to solve this problem. One of them is discussed in
the next section.
|