completed 20, multithreading; 21, functiontemplates next

This commit is contained in:
Frank B. Brokken 2017-06-02 18:26:30 +02:00
parent cc6d480d8c
commit 73680fc983
14 changed files with 67 additions and 45 deletions

View file

@ -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

View file

@ -41,6 +41,7 @@ IFDEF(html)(\
IFDEF(latex)
(\
SUBST(--)(-{}-{})\
redef(XXsetlatexdocumentheader)(3)(\
IFEMPTY(ARG1)
(\

View file

@ -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.

View file

@ -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
&currentValue, 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

View file

@ -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)

View file

@ -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.

View 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();
};

View file

@ -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));
)
)

View file

@ -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

View file

@ -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(

View file

@ -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.

View file

@ -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.
)

View file

@ -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)

View file

@ -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