~ubuntu-branches/ubuntu/trusty/c++-annotations/trusty-proposed

« back to all changes in this revision

Viewing changes to yo/stl/events.yo

  • Committer: Package Import Robot
  • Author(s): Frank B. Brokken
  • Date: 2013-05-30 13:32:18 UTC
  • mfrom: (1.1.24)
  • Revision ID: package-import@ubuntu.com-20130530133218-k39mr5uredd093jr
Tags: 9.7.2-1
New upstream release, repairs several minor left-over flaws.
This release incorporates 9.7.0 and 9.7.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
Consider a classic producer-consumer case: the producer generates items to be
2
 
consumed by a consumer. The producer can only produce so many items before its
3
 
storage capacity has filled up and the client cannot consume more items than
4
 
the producer has produced.
5
 
 
6
 
At some point the producer will therefore have to wait until the client has
7
 
consumed enough so there's again space available in the producer's
8
 
storage. Similarly, the client will have to wait until the producer has
9
 
produced at least some items.
10
 
 
11
 
Locking and polling the amount of available items/storage at fixed time
12
 
intervals usually isn't a good option as it is in essence a wasteful scheme:
13
 
threads continue to wait during the full interval even though the condition to
14
 
continue may already have been met; reducing the interval, on the other hand,
15
 
isn't an attractive option either as it results in a relative increase of the
16
 
overhead associated with handling the associated mutexes.
17
 
 
18
 
Condition variables hi(condition_variable) may be used to solve these
19
 
kinds of problems. A thread simply sleeps until it is notified by another
20
 
thread. Generally this may be accomplished as follows:
21
 
    verb(
22
 
    producer loop:
23
 
        - produce the next item
24
 
        - wait until there's room to store the item,
25
 
            then reduce the available room
26
 
        - store the item
27
 
        - increment the number of items in store
28
 
 
29
 
    consumer loop:
30
 
        - wait until there's an item in store,
31
 
            then reduce the number of items in store
32
 
        - remove the item from the store
33
 
        - increment the number of available storage locations
34
 
        - do something with the retrieved item
35
 
    )
36
 
 
37
 
It is important that the two storage administrative tasks (registering the
38
 
number of available items and available storage locations) are either
39
 
performed by the client or by the producer. `Waiting' in this case means:
40
 
    itemization(
41
 
    it() Get a lock on the variable containing the actual count
42
 
    it() As long as the count is zero: wait, release the lock until
43
 
        another thread has increased the count, re-acquire the lock.
44
 
    it() Reduce the count
45
 
    it() Release the lock.
46
 
    )
47
 
    To implement this scheme a ti(condition_variable) is used. The variable
48
 
containing the actual count is called a ti(semaphore) and it can be protected
49
 
using a tt(mutex sem_mutex). In addition a tt(condition_variable condition) is
50
 
defined. The waiting process is defined in the following function tt(down)
51
 
implemented as follows:
52
 
        verb(
53
 
    void down()
54
 
    {
55
 
        unique_lock<mutex> lock(sem_mutex);   // get the lock
56
 
        while (semaphore == 0)
57
 
            condition.wait(lock);           // see 1, below.
58
 
        --semaphore;                        // dec. semaphore count
59
 
    }                                       // the lock is released
60
 
        )
61
 
    At 1 the condition variable's tt(wait) member internally releases the
62
 
lock, wait for a notification to continue, and re-acquires the lock just
63
 
before returning. Consequently, tt(down)'s code always has complete and
64
 
unique control over tt(semaphore).
65
 
 
66
 
    What about notifying the condition variable? This is handled by the
67
 
`increment the number ...' parts in the abovementioned produced and consumer
68
 
loops. These parts are defined by the following tt(up) function:
69
 
        verb(
70
 
    void up()
71
 
    {
72
 
        lock_guard<std::mutex> lock(sem_mutex); // get the lock
73
 
        if (semaphore++ == 0)
74
 
            condition.notify_one();             // see 2, below
75
 
    }                                           // the lock is released
76
 
        )
77
 
    At 2 tt(semaphore) is always incremented. However, by using a postfix
78
 
increment it can be tested for being zero at the same time and if it was zero
79
 
initially then tt(semaphore) is now one. Consequently, the thread waiting for
80
 
tt(semaphore) being unequal to zero may now
81
 
continue. tt(Condition.notify_one)hi(notify_one) notifies a waiting thread
82
 
(see tt(down)'s implementation above). In situations where multiple threads
83
 
are waiting `ti(notify_all)' can be used.
84
 
 
85
 
    Handling tt(semaphore) can very well be encapsulated  in a class
86
 
tt(Semaphore), offering members tt(down) and tt(up). For a more extensive
87
 
discussion of semaphores see i(Tanenbaum, A.S.) (2006)
88
 
    i(Structured Computer Organization), Pearson Prentice-Hall.
89
 
 
90
 
    Using the semaphore facilities of the class tt(Semaphore) whose
91
 
constructor expects an initial value of its tt(semaphore) data member, the
92
 
classic producer and consumer case can now easily be implemented in the
93
 
following multi-threaded program+footnote(A more elaborate example of the
94
 
producer-consumer program is found in the tt(yo/stl/examples/events.cc) file
95
 
in the bf(C++) Annotations's source archive):
96
 
        verb(
97
 
    Semaphore available(10);
98
 
    Semaphore filled(0);
99
 
    std::queue itemQueue;
100
 
 
101
 
    void producer()
102
 
    {
103
 
        size_t item = 0;
104
 
        while (true)
105
 
        {
106
 
            ++item;
107
 
            available.down();
108
 
            itemQueue.push(item);
109
 
            filled.up();
110
 
        }
111
 
    }
112
 
    void client()
113
 
    {
114
 
        while (true)
115
 
        {
116
 
            filled.down();
117
 
            size_t item = itemQueue.front();
118
 
            itemQueue.pop();
119
 
            available.up();
120
 
            process(item);      // not implemented here
121
 
        }
122
 
    }
123
 
 
124
 
    int main()
125
 
    {
126
 
        thread produce(producer);
127
 
        thread consume(consumer);
128
 
 
129
 
        produce.join();
130
 
        return 0;
131
 
    }
132
 
        )
133
 
 
134
 
COMMENT(
135
 
 
136
 
You still need to check that the data is ready though, since condition
137
 
variables can suffer from what are called spurious wakes: The call to wait()
138
 
may return even though it wasn't notified by another thread. If you're worried
139
 
about getting this wrong, you can pass that responsibility off to the standard
140
 
library too, if you tell it what you're waiting for with a predicate. The new
141
 
C++11 lambda facility makes this really easy:
142
 
    void foo()
143
 
    {
144
 
        std::unique_lock<std::mutex> lk(m);
145
 
        cond.wait(lk,[]{return data_ready;});
146
 
        process_data();
147
 
    }
148
 
END)
149
 
 
150
 
To use tt(condition_variable) objects the header file
151
 
    ti(condition_variable)hi(#include <condition_variable>) must be included.
 
1
In this section em(condition variables) are introduced, allowing programs to
 
2
synchronize threads on the em(states) of data, rather than on the em(access)
 
3
to data, which is realized using  mutexes.
 
4
 
 
5
Before using condition variables the tthi(condition_variable) header file must
 
6
have been included.
 
7
 
 
8
To start our discussion, we consider a classic producer-consumer scenario: the
 
9
producer generates items to be consumed by a consumer. The producer can only
 
10
produce a certain number of items before its storage capacity has filled up
 
11
and the client cannot consume more items than the producer has produced.
 
12
 
 
13
At some point the producer has to wait until the client has consumed enough,
 
14
thus creating space in the producer's storage. Similarly, the consumer cannot
 
15
start consuming until the producer has at least produced some items.
 
16
 
 
17
Mutexes (data locking) don't result in elegant solutions of producer-consumer
 
18
types of problems, as using mutexes requires repeated locking and polling the
 
19
amount of available items/storage. This isn't a very attractive option as it
 
20
wastes resources. Polling forces threads to wait until they own the mutex,
 
21
even though continuation might already be possible. The polling interval could
 
22
be reduced, but that too isn't an attractive option, as it results in
 
23
needlessly increasing the overhead associated with handling the associated
 
24
mutexes.
 
25
 
 
26
On the other hand, condition variables allow you to avoid polling by
 
27
synchronizing threads using the em(states) (e.g., em(values)) of data.
 
28
 
 
29
As the the states of the data may be modified by multiple threads, threads
 
30
still have to use mutexes, but merely to control access to the data. However,
 
31
condition variables allow threads to release ownership of the mutex until a
 
32
certain state has been reached, until a preset amount of time has been passed,
 
33
or until a preset point in time has been reached.
 
34
 
 
35
The prototypical setup of these kinds of programs look like this:
 
36
    itemization(
 
37
    it() consumer thread(s) act like this:
 
38
        verb(
 
39
    obtain ownership of the used mutex
 
40
    while the required condition is false:
 
41
        release the ownership and wait until being notified
 
42
    continue processing now that the condition is true
 
43
    release ownership of the mutex
 
44
        )
 
45
    it() producer thread(s) act like this:
 
46
        verb(
 
47
    obtain ownership of the used mutex 
 
48
    while the condition is false:
 
49
        work towards changing the condition to true
 
50
    signal other waiting threads that the condition is now true
 
51
    release ownership of the mutex
 
52
        )
 
53
    )
 
54
 
 
55
Condition variables come in two flavors: objects of the class 
 
56
    hi(condition_variable)tt(std::condition_variable) are used in combination
 
57
with objects of type tt(unique_lock<mutex>). This combination allows
 
58
optimizations resulting in an increased efficiency compared to the efficiency
 
59
that can be obtained with objects of the class
 
60
    hi(condition_variable_any)tt(stdLLcondition_variable_any) that can be used
 
61
with any (e.g., user-supplied) lock types.
 
62
 
 
63
The condition variable classes offer members like tt(wait, wait_for,
 
64
wait_until, notify_one) and tt(notify_all) that may concurrently be called.
 
65
The notify members are always atomically executed. Execution of the
 
66
tt(wait) members consists of three atomic parts:
 
67
    itemization(
 
68
    it() the mutex's release, and subsequent entry into the waiting state;
 
69
    it() unblocking the wait state;
 
70
    it() reacquisition of the lock.
 
71
    )
 
72
    Therefore, returning from tt(wait)-members the thread calling wait owns
 
73
the lock.
 
74
 
 
75
Programs using condition variables cannot make any assumption about the order
 
76
in which any of the tt(notify_one, notify_all, wait, wait_for), and
 
77
tt(wait_until) members are executed.
 
78
 
 
79
In addition to the condition variable classes the following free function and
 
80
tt(enum) type are provided:
 
81
    itemization(
 
82
    itht(notify_all_at_thread_exit)(void 
 
83
            std::notify_all_at_thread_exit+OPENPARcondition_variable &cond,)
 
84
            linebreak()tt(unique_lock<mutex> lockObject+CLOSEPAR:)
 
85
       quote(once the current thread has ended, all other threads waiting on
 
86
        tt(cond) will be notified. It is good practice to exit the thread as
 
87
        soon as possible after calling linebreak()
 
88
        tt(notify_all_at_thread_exit).
 
89
 
 
90
       Waiting threads must verify that the thread they were waiting for has
 
91
        indeed ended. This is usually implemented by obtaining the lock on
 
92
        tt(lockObject), after which these threads verify that the condition
 
93
        they were waiting for is true, and that the lock was not released and
 
94
        reacquired before tt(notify_all_at_thread_exit) was called.)
 
95
    it() hi(cv_status)
 
96
        quote(
 
97
            The tt(cv_status) enum is used by several member functions of the
 
98
condition variable classes covered below:
 
99
        verb(
 
100
    namespace std
 
101
    {
 
102
        enum class cv_status 
 
103
        { 
 
104
            no_timeout, 
 
105
            timeout 
 
106
        };
 
107
    }
 
108
        )
 
109
            )
 
110
    )