18.3 Starting a Thread in Detail
Having introduced the high- and low-level interfaces to (possibly) start threads and deal with return
values or exceptions, let’s summarize the concepts and provide some details not mentioned yet.
call
std::async()
return values or exceptions automatically are
provided by a std::future
Starting
the Thread
Returning
Exceptions
create object of class
std::thread
set return values or exceptions in a
std::promise
and process it by a std::future
call task of class
std::packaged_task
return values or exceptions automatically are
provided by a std::future
Returning
Values
use a shared state
create object of class
std::thread
through type
std::exception_ptr
use shared variables
(synchronization required)
Figure 18.1. Layers of Thread Interfaces
Conceptionally, we have the following layers to start threads and deal with their return values or
exceptions (see Figure 18.1):
• With the low-level interface of class thread, we can start a thread. To return data, we need
shared variables (global or static or passed as argument). To return exceptions, we could use the
type std::exception_ptr, which is returned by std::current_exception() and can be
processed by std::rethrow_exception() (see Section 4.3.3, page 52).
• The concept of a shared state allows us to deal with return values or exceptions in a more convenient
way. With the low-level interface of a promise, we can create such a shared state, which
we can process by using a future.
• At a higher level, with class packaged_task or async(), the shared state is automatically
created and set with a return statement or an uncaught exception.
• With packaged_task, we can create an object with a shared state where we explicitly have to
program when to start the thread.
• With std::async(), we don’t have to care when the thread exactly gets started. The only thing
we know is that we have to call get() when we need the outcome.
Shared States
As you can see, a central concept used by almost all these features is a shared state. It allows the
objects that start and control a background functionality (a promise, a packaged task, or async()) to
www.it-ebooks.info
974 Chapter 18: Concurrency
communicate with the objects that process its outcome (a future or a shared future). Thus, a shared
state is able to hold the functionality to start, some state information, and its outcome (a return value
or an exception).
A shared state is ready when it holds the outcome of its functionality (when a value or an exception
is ready for retrieval). A shared state is usually implemented as a reference-counted object that
gets destroyed when the last object referring to it releases it.
18.3.1 async() in Detail
In general, as introduced in Section 18.1, page 946, std::async() is a convenience function to
start some functionality in its own thread if possible. As a result, you can parallelize functionality if
the underlying platform supports it but not lose any functionality if it doesn’t.
However, the exact behavior of async() is complex and highly depends on the launch policy,
which can be passed as the first optional argument. For this reason, each of the three standardized
forms of how async() can be called as described here from an application programmer’s point of
view:
future async (std::launch::async, F func, args...)
• Tries to start func with args as an asynchronous task (parallel thread).
• If this is not possible, it throws an exception of type std::system_error with the error code
std::errc::resource_unavailable_try_again (see Section 4.3.1, page 43).
• Unless the program aborts, the started thread is guaranteed to finish before the program ends.
• The thread will finish:
– If get() or wait() is called for the returned future
– If the last object that refers to the shared state represented by the returned future gets
destructed
• This implies that the call of async() will block until func has finished if the return value of
async() is not used.
future async (std::launch::deferred, F func, args...)
• Passes func with args as a “deferred” task, which gets synchronously called when wait() or
get() for the returned future gets called.
• If neither wait() nor get() is called, the task will never start.
future async (F func, args...)
• Is a combination of calling async() with launch policies std::launch:async and
std::launch::deferred. According to the current situation, one of the two forms gets chosen.
Thus, async() will defer the call of func if an immediate call in async launch policy is not
possible.
• Thus, if async() can start a new thread for func, it gets started. Otherwise, func is deferred until
get() or wait() gets called for the returned future.
www.it-ebooks.info
18.3 Starting a Thread in Detail 975
• The only guarantee this call gives is that after calling get() or wait() for the returned future,
func will have been called and finished.
• Without calling get() or wait() for the returned future, func might never get called.
• Note that this form of async() will not throw a system_error exception if it can’t call func
asynchronously (it might throw a system error for other reasons, though).
For all these forms of async(), func might be a callable object (function, member function, function
object, lambda; see Section 4.4, page 54). See Section 18.1.2, page 958, for some examples.
Passing a launch policy of std::launch::async|std::launch::deferred to async() results
in the same behavior as passing no launching policy. Passing 0 as launch policy results in
undefined behavior (this case is not covered by the C++ standard library, and different implementations
behave differently).
18.3.2 Futures in Detail
Class future,10 introduced in Section 18.1, page 946, represents the outcome of an operation. It
can be a return value or an exception but not both. The outcome is managed in a shared state, which
in general can be created by std::async(), a std::packaged_task, or a promise. The outcome
might not exist yet; thus, the future might also hold everything necessary to generate the outcome.
If the future was returned by async() (see Section 18.3.1, page 974) and the associated task was
deferred, get() or wait() will start it synchronously. Note that wait_for() and wait_until()
do not start a deferred task.
The outcome can be retrieved only once. For this reason, a future might have a valid or invalid
state: valid means that there is an associated operation for which the result or exception was not
retrieved yet.
Table 18.1 lists the operations available for class future.
Note that the return value of get() depends on the type future is specialized with:
• If it is void, get() also has type void and returns nothing.
• If the future is parametrized with a reference type, get() returns a reference to the return value.
• Otherwise, get() returns a copy or move assigns the return value, depending on whether the
return type supports move assignment semantics.
Note that you can call get() only once, because get() invalidates the future’s state.
For a future that has an invalid state, calling anything else but the destructor, the move assignment
operator, or valid() results in undefined behavior. For this case, the standard recommends
throwing an exception of type future_error (see Section 4.3.1, page 43) with the code
std::future_errc::no_state, but this is not required.
Note that neither a copy constructor nor a copy assignment operator is provided, ensuring that no
two objects can share the state of a background operation. You can move the state to another future
object only by calling the move constructor or the move assignment operator. However, the state
10 Originally, the class was named unique_future in the standardization process.
www.it-ebooks.info
976 Chapter 18: Concurrency
Operation Effect
future f Default constructor; creates a future with an invalid state
future f(rv) Move constructor; creates a new future, which gets the state
of rv, and invalidates the state of rv
f.~future() Destroys the state and destroys *this
f = rv Move assignment; destroys the old state of f, gets the state
of rv, and invalidates the state of rv
f.valid() Yields true if f has a valid state, so you can call the
following member functions
f.get() Blocks until the background operation is done (forcing a
deferred associated functionality to start synchronously),
yields the result (if any) or raises any exception that
occurred, and invalidates its state
f.wait() Blocks until the background operation is done (forcing a
deferred associated functionality to start synchronously)
f.wait_for(dur) Blocks for duration dur or until the background operation is
done (a deferred thread is not forced to start)
f.wait_until(tp) Blocks until timepoint tp or until the background operation
is done (a deferred thread is not forced to start)
f.share() Yields a shared_future with the current state and
invalidates the state of f
Table 18.1. Operations of Class future
of background tasks can be shared in multiple objects by using a shared_future object, which
share() yields.
If the destructor is called for a future that is the last owner of a shared state and the associated
task has started but not finished yet, the destructor blocks until the end of the task.
18.3.3 Shared Futures in Detail
Class shared_future (introduced in Section 18.1.3, page 960) provides the same semantics and
interface as class future (see Section 18.3.2, page 975) with the following differences:
• Multiple calls of get() are allowed. Thus, get() does not invalidate its state.
• Copy semantics (copy constructor, copy assignment operator) are supported.
• get() is a constant member function returning a const reference to the value stored in the
shared state (which means that you have to ensure that the lifetime of the returned reference is
shorter than the shared state). For class std::future, get() is a nonconstant member function
returning a move-assigned copy (or a copy if th
18.3 Starting a Thread in Detail
Having introduced the high- and low-level interfaces to (possibly) start threads and deal with return
values or exceptions, let’s summarize the concepts and provide some details not mentioned yet.
call
std::async()
return values or exceptions automatically are
provided by a std::future<>
Starting
the Thread
Returning
Exceptions
create object of class
std::thread
set return values or exceptions in a
std::promise<>
and process it by a std::future<>
call task of class
std::packaged_task
return values or exceptions automatically are
provided by a std::future<>
Returning
Values
use a shared state
create object of class
std::thread
through type
std::exception_ptr
use shared variables
(synchronization required)
Figure 18.1. Layers of Thread Interfaces
Conceptionally, we have the following layers to start threads and deal with their return values or
exceptions (see Figure 18.1):
• With the low-level interface of class thread, we can start a thread. To return data, we need
shared variables (global or static or passed as argument). To return exceptions, we could use the
type std::exception_ptr, which is returned by std::current_exception() and can be
processed by std::rethrow_exception() (see Section 4.3.3, page 52).
• The concept of a shared state allows us to deal with return values or exceptions in a more convenient
way. With the low-level interface of a promise, we can create such a shared state, which
we can process by using a future.
• At a higher level, with class packaged_task or async(), the shared state is automatically
created and set with a return statement or an uncaught exception.
• With packaged_task, we can create an object with a shared state where we explicitly have to
program when to start the thread.
• With std::async(), we don’t have to care when the thread exactly gets started. The only thing
we know is that we have to call get() when we need the outcome.
Shared States
As you can see, a central concept used by almost all these features is a shared state. It allows the
objects that start and control a background functionality (a promise, a packaged task, or async()) to
www.it-ebooks.info
974 Chapter 18: Concurrency
communicate with the objects that process its outcome (a future or a shared future). Thus, a shared
state is able to hold the functionality to start, some state information, and its outcome (a return value
or an exception).
A shared state is ready when it holds the outcome of its functionality (when a value or an exception
is ready for retrieval). A shared state is usually implemented as a reference-counted object that
gets destroyed when the last object referring to it releases it.
18.3.1 async() in Detail
In general, as introduced in Section 18.1, page 946, std::async() is a convenience function to
start some functionality in its own thread if possible. As a result, you can parallelize functionality if
the underlying platform supports it but not lose any functionality if it doesn’t.
However, the exact behavior of async() is complex and highly depends on the launch policy,
which can be passed as the first optional argument. For this reason, each of the three standardized
forms of how async() can be called as described here from an application programmer’s point of
view:
future async (std::launch::async, F func, args...)
• Tries to start func with args as an asynchronous task (parallel thread).
• If this is not possible, it throws an exception of type std::system_error with the error code
std::errc::resource_unavailable_try_again (see Section 4.3.1, page 43).
• Unless the program aborts, the started thread is guaranteed to finish before the program ends.
• The thread will finish:
– If get() or wait() is called for the returned future
– If the last object that refers to the shared state represented by the returned future gets
destructed
• This implies that the call of async() will block until func has finished if the return value of
async() is not used.
future async (std::launch::deferred, F func, args...)
• Passes func with args as a “deferred” task, which gets synchronously called when wait() or
get() for the returned future gets called.
• If neither wait() nor get() is called, the task will never start.
future async (F func, args...)
• Is a combination of calling async() with launch policies std::launch:async and
std::launch::deferred. According to the current situation, one of the two forms gets chosen.
Thus, async() will defer the call of func if an immediate call in async launch policy is not
possible.
• Thus, if async() can start a new thread for func, it gets started. Otherwise, func is deferred until
get() or wait() gets called for the returned future.
www.it-ebooks.info
18.3 Starting a Thread in Detail 975
• The only guarantee this call gives is that after calling get() or wait() for the returned future,
func will have been called and finished.
• Without calling get() or wait() for the returned future, func might never get called.
• Note that this form of async() will not throw a system_error exception if it can’t call func
asynchronously (it might throw a system error for other reasons, though).
For all these forms of async(), func might be a callable object (function, member function, function
object, lambda; see Section 4.4, page 54). See Section 18.1.2, page 958, for some examples.
Passing a launch policy of std::launch::async|std::launch::deferred to async() results
in the same behavior as passing no launching policy. Passing 0 as launch policy results in
undefined behavior (this case is not covered by the C++ standard library, and different implementations
behave differently).
18.3.2 Futures in Detail
Class future<>,10 introduced in Section 18.1, page 946, represents the outcome of an operation. It
can be a return value or an exception but not both. The outcome is managed in a shared state, which
in general can be created by std::async(), a std::packaged_task, or a promise. The outcome
might not exist yet; thus, the future might also hold everything necessary to generate the outcome.
If the future was returned by async() (see Section 18.3.1, page 974) and the associated task was
deferred, get() or wait() will start it synchronously. Note that wait_for() and wait_until()
do not start a deferred task.
The outcome can be retrieved only once. For this reason, a future might have a valid or invalid
state: valid means that there is an associated operation for which the result or exception was not
retrieved yet.
Table 18.1 lists the operations available for class future<>.
Note that the return value of get() depends on the type future<> is specialized with:
• If it is void, get() also has type void and returns nothing.
• If the future is parametrized with a reference type, get() returns a reference to the return value.
• Otherwise, get() returns a copy or move assigns the return value, depending on whether the
return type supports move assignment semantics.
Note that you can call get() only once, because get() invalidates the future’s state.
For a future that has an invalid state, calling anything else but the destructor, the move assignment
operator, or valid() results in undefined behavior. For this case, the standard recommends
throwing an exception of type future_error (see Section 4.3.1, page 43) with the code
std::future_errc::no_state, but this is not required.
Note that neither a copy constructor nor a copy assignment operator is provided, ensuring that no
two objects can share the state of a background operation. You can move the state to another future
object only by calling the move constructor or the move assignment operator. However, the state
10 Originally, the class was named unique_future in the standardization process.
www.it-ebooks.info
976 Chapter 18: Concurrency
Operation Effect
future f Default constructor; creates a future with an invalid state
future f(rv) Move constructor; creates a new future, which gets the state
of rv, and invalidates the state of rv
f.~future() Destroys the state and destroys *this
f = rv Move assignment; destroys the old state of f, gets the state
of rv, and invalidates the state of rv
f.valid() Yields true if f has a valid state, so you can call the
following member functions
f.get() Blocks until the background operation is done (forcing a
deferred associated functionality to start synchronously),
yields the result (if any) or raises any exception that
occurred, and invalidates its state
f.wait() Blocks until the background operation is done (forcing a
deferred associated functionality to start synchronously)
f.wait_for(dur) Blocks for duration dur or until the background operation is
done (a deferred thread is not forced to start)
f.wait_until(tp) Blocks until timepoint tp or until the background operation
is done (a deferred thread is not forced to start)
f.share() Yields a shared_future with the current state and
invalidates the state of f
Table 18.1. Operations of Class future<>
of background tasks can be shared in multiple objects by using a shared_future object, which
share() yields.
If the destructor is called for a future that is the last owner of a shared state and the associated
task has started but not finished yet, the destructor blocks until the end of the task.
18.3.3 Shared Futures in Detail
Class shared_future<> (introduced in Section 18.1.3, page 960) provides the same semantics and
interface as class future (see Section 18.3.2, page 975) with the following differences:
• Multiple calls of get() are allowed. Thus, get() does not invalidate its state.
• Copy semantics (copy constructor, copy assignment operator) are supported.
• get() is a constant member function returning a const reference to the value stored in the
shared state (which means that you have to ensure that the lifetime of the returned reference is
shorter than the shared state). For class std::future, get() is a nonconstant member function
returning a move-assigned copy (or a copy if th
การแปล กรุณารอสักครู่..