mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-16 07:48:44 +01:00
completed 20, multithreading; 21, functiontemplates next
This commit is contained in:
parent
cc6d480d8c
commit
73680fc983
14 changed files with 67 additions and 45 deletions
|
@ -1,6 +1,8 @@
|
|||
Structured binding declarations (cf https://en.wikipedia.org/wiki/C++17 en
|
||||
~/downloads/n4659.pdf
|
||||
|
||||
check in multithreading/quicksort whether annotations() has been converted.
|
||||
|
||||
Add weak_ptr
|
||||
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ IFDEF(html)(\
|
|||
|
||||
IFDEF(latex)
|
||||
(\
|
||||
SUBST(--)(-{}-{})\
|
||||
redef(XXsetlatexdocumentheader)(3)(\
|
||||
IFEMPTY(ARG1)
|
||||
(\
|
||||
|
|
|
@ -29,7 +29,7 @@ started using tt(std::thread): it is passed a function and optionally
|
|||
arguments which are forwarded to the function.
|
||||
|
||||
Although the function implementing the asynchronous task may be passed as
|
||||
first argument, tt(async) first argument may also be a value of the strongly
|
||||
first argument, tt(async's) first argument may also be a value of the strongly
|
||||
typed enumeration hi(deferred)hi(async)hi(launch)tt(std::launch):
|
||||
verb(
|
||||
enum class launch
|
||||
|
@ -149,5 +149,5 @@ The tt(std::async) function template is used to start a thread, making its
|
|||
results available to the calling thread. On the other hand, we may only be
|
||||
able to em(prepare) (package) a task (a thread), but may have to leave the
|
||||
completion of the task to another thread. Scenarios like this are realized
|
||||
through objects of the class tt(std::package_task), which is the topic of the
|
||||
through objects of the class tt(std::packaged_task), which is the topic of the
|
||||
next section.
|
||||
|
|
|
@ -68,7 +68,7 @@ constants, which are used to specify ordering constraints of atomic operations:
|
|||
itt(memory_order_release:) the operation is a release operation. It
|
||||
synchronizes with acquire operations on the same location;
|
||||
itt(memory_order_sec_cst:) the default memory order specification for all
|
||||
operations. Memmory storing operations use tt(memory_order_release),
|
||||
operations. Memory storing operations use tt(memory_order_release),
|
||||
memory load operations use tt(memory_order_acquire), and
|
||||
read-modify-write operations use tt(memory_order_acq_rel).
|
||||
)
|
||||
|
@ -84,18 +84,19 @@ Here are the standard available tt(std::atomic<Type>) member functions:
|
|||
ithtq(compare_exchange_strong)(bool compare_exchange_strong(Type
|
||||
¤tValue, Type newValue) noexcept)
|
||||
(The value in the atomic object is compared to tt(newValue) using
|
||||
byte-wise comparisons. If equal tt(true) is returned, and
|
||||
tt(newValue) is stored in the atomic object; if unequal tt(false) is
|
||||
returned and the object's current value is stored in
|
||||
byte-wise comparisons. If equal (and tt(true) is returned) then
|
||||
tt(newValue) is stored in the atomic object; if unequal (and tt(false)
|
||||
is returned) the object's current value is stored in
|
||||
tt(currentValue);)
|
||||
|
||||
ithtq(compare_exchange_weak)(bool compare_exchange_weak(Type &oldValue,
|
||||
Type newValue) noexcept)
|
||||
(The value in the atomic object is compared to tt(newValue) using
|
||||
byte-wise comparisons. If equal tt(true) is returned, and tt(newValue)
|
||||
is stored in the atomic object; if unequal, or tt(newValue) cannot be
|
||||
atomically assigned to the current object tt(false) is returned and
|
||||
the object's current value is stored in tt(currentValue);)
|
||||
byte-wise comparisons. If equal (and tt(true) is returned),
|
||||
then tt(newValue) is stored in the atomic object; if unequal, or
|
||||
tt(newValue) cannot be atomically assigned to the current object
|
||||
tt(false) is returned and the object's current value is stored in
|
||||
tt(currentValue);)
|
||||
|
||||
ithtq(exchange)(Type exchange(Type newValue) noexcept)
|
||||
(The object's current value is returned, and tt(newValue) is assigned
|
||||
|
|
|
@ -3,13 +3,13 @@ illustrates the use of tt(packaged_tasks).
|
|||
|
||||
Like the multi-threaded quicksort example a worker pool is used. However, in
|
||||
this example the workers in fact do not know what their task is. In the
|
||||
current example the tasks happens to be identical, but diffent tasks might as
|
||||
well have been used, without the need to update the workers.
|
||||
current example the tasks happens to be identical, but different tasks might
|
||||
as well have been used, without having to update the workers.
|
||||
|
||||
The program uses a tt(class Task) containing a command-specification
|
||||
(tt(d_command), and a task specification (tt(d_task)) (cf. fig(compile), the
|
||||
(tt(d_command)), and a task specification (tt(d_task)) (cf. fig(compile)), the
|
||||
sources of the program are found in the
|
||||
tt(yo/threading/examples/multicompile) directory) of the annotations()).
|
||||
tt(yo/threading/examples/multicompile) directory of the annotations().
|
||||
|
||||
figure(threading/compile)
|
||||
(Data structure used for the multi-threading compilation)
|
||||
|
@ -32,7 +32,7 @@ Next the dispatcher. It ignores empty lines. Also, if a compilation has
|
|||
failed by the time the dispatcher is called, processing stops (lines
|
||||
6-7). Otherwise, the dispacher waits for an available worker, prepares a new
|
||||
task, and notifies a worker to handle it:
|
||||
verbinclude(-s4 //code examples/multicompile/dispatch.cc)
|
||||
verbinclude(-ns4 //code examples/multicompile/dispatch.cc)
|
||||
|
||||
The function tt(newTask) prepares the program for the next task. First a
|
||||
tt(Task) object is created. tt(Task) contains the name of the file to compile,
|
||||
|
@ -51,7 +51,7 @@ the result queue. Although the actual results aren't available by this time,
|
|||
the tt(result) function (see below) is notified that something has been pushed
|
||||
on its queue. Additionally, the tt(Task) itself is pushed on the task queue,
|
||||
where a worker may retrieve it:
|
||||
verbinclude(-s4 //task examples/multicompile/main.ih)
|
||||
verbinclude(-as4 examples/multicompile/main2.ih)
|
||||
|
||||
verbinclude(-s4 //code examples/multicompile/pushresultq.cc)
|
||||
|
||||
|
@ -85,7 +85,7 @@ yet been pushed on the result queue tt(newResult) waits. It also waits when
|
|||
there are no results available in the results queue, but at least one worker
|
||||
thread is busy. Whenever a result is pushed on the result queue, and also once
|
||||
the input stream has been processed tt(newResult) is notified. Following the
|
||||
notification tt(newResults) returns, and tt(results) either ends or shows the
|
||||
notification tt(newResult) returns, and tt(results) either ends or shows the
|
||||
results appearing at te result queue's front:
|
||||
verbinclude(-s4 //code examples/multicompile/newresult.cc)
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ tt(wait) and tt(notify_all). For a more extensive discussion of semaphores see
|
|||
em(i(Tanenbaum, A.S.) and i(Austin, T.)) (2013)
|
||||
i(Structured Computer Organization), Pearson Prentice-Hall.
|
||||
|
||||
The data member containing the actual count is called tt(d_semaphore). It is
|
||||
The data member containing the actual count is called tt(d_available). It is
|
||||
protected by tt(mutex d_mutex). In addition a tt(condition_variable
|
||||
d_condition) is defined:
|
||||
verbinclude(-s4 //data examples/events.cc)
|
||||
|
@ -43,14 +43,14 @@ The waiting process is implemented through its member function tt(wait):
|
|||
verbinclude(-nNs4 //wait examples/events.cc)
|
||||
In line 5 tt(d_condition.wait) releases the lock. It waits until receiving
|
||||
a notification, and re-acquires the lock just before returning. Consequently,
|
||||
tt(wait's) code always has complete and unique control over tt(d_semaphore).
|
||||
tt(wait's) code always has complete and unique control over tt(d_available).
|
||||
|
||||
What about notifying the a waiting thread? This is handled in lines 4 and
|
||||
5 of the member function tt(notify_all):
|
||||
verbinclude(-nNs4 //notify_all examples/events.cc)
|
||||
At line 4 tt(d_semaphore) is always incremented; by using a postfix
|
||||
At line 4 tt(d_available) is always incremented; by using a postfix
|
||||
increment it can simultaneously be tested for being zero. If it was initially
|
||||
zero then tt(d_semaphore) is now one. A thread waiting until tt(d_semaphore)
|
||||
zero then tt(d_available) is now one. A thread waiting until tt(d_available)
|
||||
exceeds zero may now continue. A waiting thread is notified by calling
|
||||
tt(d_condition.notify_one). In situations where multiple threads are waiting
|
||||
`ti(notify_all)' can also be used.
|
||||
|
|
12
annotations/yo/threading/examples/multicompile/main2.ih
Normal file
12
annotations/yo/threading/examples/multicompile/main2.ih
Normal file
|
@ -0,0 +1,12 @@
|
|||
class Task
|
||||
{
|
||||
string d_command;
|
||||
PackagedTask d_task;
|
||||
|
||||
public:
|
||||
Task() = default;
|
||||
|
||||
Task(string const &command, PackagedTask &&tmp);
|
||||
void operator()();
|
||||
shared_future<Result> result();
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
tt(Std::promise's) member tt(set_exception) does not expect an
|
||||
tt(Std::promise's) member tt(set_exception) does not expect a
|
||||
tt(std::exception) argument, but an object of the class
|
||||
hi(exception_ptr)tt(std::exception_ptr). In this section we take a closer look
|
||||
at the class tt(exception_ptr).
|
||||
|
@ -6,7 +6,7 @@ at the class tt(exception_ptr).
|
|||
Before using the class tt(exception_ptr) the tthi(future) header file
|
||||
must be included.
|
||||
|
||||
The class an tt(exception_ptr's) default constructor initializes it to a
|
||||
The class tt(exception_ptr's) default constructor initializes it to a
|
||||
null-pointer. In the following code snippet the variable tt(isNull) is set to
|
||||
true:
|
||||
verb( std::exception_ptr obj;
|
||||
|
@ -65,9 +65,9 @@ constructing or handling tt(exception_ptr) objects:
|
|||
verb(
|
||||
auto ptr = make_exception_ptr(exception());
|
||||
ptr = make_exception(string("hello world"));
|
||||
ptr = make_exception(12));
|
||||
ptr = make_exception(12);
|
||||
|
||||
obj.set_exception(make_exception_ptr(ptr);
|
||||
obj.set_exception(make_exception_ptr(ptr));
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ Before using the hi(once_flag)tt(std::once_flag) and the
|
|||
hi(call_once)tt(std::call_once) function, introduced in this section, the
|
||||
tthi(mutex) header file must be included.
|
||||
|
||||
In single threaded programs the initialization of global data not necessarily
|
||||
happens at the same point in code. An example is the initialization of the
|
||||
object of a singleton class (cf. em(Gamma et al.) (1995), Design Patterns,
|
||||
In single threaded programs the initialization of global data does not
|
||||
necessarily occur at one point in code. An example is the initialization of
|
||||
the object of a singleton class (cf. em(Gamma et al.) (1995), Design Patterns,
|
||||
Addison-Wesley). Singleton classes may define a single static pointer data
|
||||
member tt(Singleton *s_object), pointing to the singleton's object, and may
|
||||
offer a static member tt(instance), implemented something like this:
|
||||
|
@ -73,8 +73,9 @@ However, there are additional ways to initialize data, even for multi-threaded p
|
|||
keyword (cf. section ref(CONSTEXPR)), satisfying the requirements for constant
|
||||
initialization. In this case, a static object, initialized using that
|
||||
constructor, is guaranteed to be initialized before any code is run as part of
|
||||
the static initialization phase. This used by tt(std::mutex), as it eliminates
|
||||
the possibility of race conditions when global mutexes are initialized.
|
||||
the static initialization phase. This is used by tt(std::mutex), as it
|
||||
eliminates the possibility of race conditions when global mutexes are
|
||||
initialized.
|
||||
|
||||
it() Second, a static variable defined within a compound statement may be
|
||||
used (e.g., a static local variable within a function body). Static
|
||||
|
|
|
@ -6,8 +6,8 @@ data, mutexes should be used to prevent threads from using the same data
|
|||
synchronously.
|
||||
|
||||
Usually locks are released at the end of action blocks. This requires explicit
|
||||
calls to the mutexes' tt(unlock) function, with introduces comparable problems
|
||||
as we've seen with the thread's tt(join) member.
|
||||
calls to the mutexes' tt(unlock) function, which introduces comparable
|
||||
problems as we've seen with the thread's tt(join) member.
|
||||
|
||||
To simplify locking and unlocking two mutex wrapper classes are available:
|
||||
itemization(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
In addition to tt(std::package_task) and tt(std::async) the class template
|
||||
In addition to tt(std::packaged_task) and tt(std::async) the class template
|
||||
hi(promise)tt(std::promise) can be used to obtain the results from a
|
||||
separate thread.
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@ The quicksort sorting algorithm (Hoare, 1962) is a well-known sorting
|
|||
algorithm. Given an array of tt(n) elements, it works like this:
|
||||
itemization(
|
||||
it() Pick an element from the array, and partition the array with respect
|
||||
to this element (call it the em(pivot element)). This leaves us with
|
||||
two (possibly empty) sub-arrays: one to the left of the pivot element,
|
||||
and one to the right of the pivot element;
|
||||
to this element (call it the em(pivot element)) (in the example below,
|
||||
assume a function tt(partition) performing the partition is
|
||||
available). This leaves us with two (possibly empty) sub-arrays: one
|
||||
to the left of the pivot element, and one to the right of the pivot
|
||||
element;
|
||||
it() Recursively perform quicksort on the left-hand sub-array;
|
||||
it() Recursively perform quicksort on the right-hand sub-array.
|
||||
)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
Thread execution may have to be suspended until a specific point in time, for
|
||||
a specific amount of time, or until some event has occurred. When specifying
|
||||
time an appropriate time unit must be selected. Units (not necessarily time
|
||||
units) are defined in the class template hi(ratio template)tt(std::ratio).
|
||||
time an appropriate time unit must be selected. Factors that can be used in
|
||||
combination with time units are defined in the class template hi(ratio
|
||||
template)tt(std::ratio). The time units themselves are defined in the
|
||||
namespace tt(chrono).
|
||||
|
||||
Before the class tt(ratio) can be used, the tthi(ratio) header file must be
|
||||
included. Usually just the tthi(chrono) header file is included, as tt(chrono)
|
||||
|
|
|
@ -121,11 +121,12 @@ hi(id)tt(thread::id()) is returned. Otherwise, the thread's unique ID
|
|||
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()).
|
||||
(Requires tt(joinable) to return tt(true). If the thread for which
|
||||
tt(join) is called hasn't finished yet then the thread calling tt(join) will
|
||||
be suspended (also called em(blocked)) 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
|
||||
|
|
Loading…
Reference in a new issue