File: mutex.yo

package info (click to toggle)
c%2B%2B-annotations 12.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 13,044 kB
  • sloc: cpp: 24,337; makefile: 1,517; ansic: 165; sh: 121; perl: 90
file content (157 lines) | stat: -rw-r--r-- 8,445 bytes parent folder | download | duplicates (2)
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
Objects of i(mutex) classes are used to protect shared data.

Before using mutexes the tthi(mutex) header file must be included.

One of the key characteristics of multi-threaded programs is that threads may
share data. Functions running as separate threads have access to all global
data, and may also share the local data of their parent threads. However,
unless proper measures are taken, this may easily result in data corruption,
as illustrated by the following simulation of some steps that could be
encountered in a multi-threaded program:
        verb(---------------------------------------------------------------------------
Time step:    Thread 1:     var        Thread 2:       description
---------------------------------------------------------------------------
    0                        5
    1           starts                                  T1 active
    2           writes var                              T1 commences writing
    3           stopped                                 Context switch
    4                                   starts          T2 active
    5                                   writes var      T2 commences writing
    6                       10          assigns 10      T2 writes 10
    7                                   stopped         Context switch
    8           assigns 12                              T1 writes 12
    9                       12
----------------------------------------------------------------------------)

In this example, threads 1 and 2 share variable tt(var), initially having
the value 5. At step 1 thread 1 starts, and starts to write a value into
tt(var). However, it is interrupted by a context switch, and thread 2 is
started (step 4). Thread 2 em(also) wants to write a value into tt(var), and
succeeds until time step 7, when another context switch takes place. By now
tt(var) is 10. However, thread 1 was also in the process of writing a value
into tt(var), and it is given a chance to complete its work: it assigns 12
to tt(var) in time step 8. Once time step 9 is reached, thread 2 proceeds on
the (erroneous) assumption that tt(var) must be equal to 10. Clearly, from the
point of view of thread 2 its data have been corrupted.

In this case data corruption was caused by multiple threads accessing the same
data in an uncontrolled way. To prevent this from happening, access to shared
data should be protected in such a way that only one thread at a time may
access the shared data.

em(Mutexes) are used to prevent the abovementioned kinds of problems by
offering a guarantee that data are only accessed by the thread that could lock
the mutex that is used to synchronize access to those data.

Exclusive data access completely depends on cooperation between the
threads. If thread 1 uses mutexes, but thread 2 doesn't, then thread 2 may
freely access the common data. Of course that's bad practice, which should be
avoided.

It is stressed that although em(using) mutexes is the programmer's
responsibility, their em(implementation) isn't: mutexes offer the necessary
atomic calls. When requesting a mutex-lock the thread is blocked (i.e., the
mutex statement does not return) until the lock has been obtained by the
requesting thread.

Apart from the class tt(std::mutex) the class hi(recursive_mutex)
tt(std::recursive_mutex) is available.  When a tt(recursive_mutex) is called
multiple times by the same thread it increases its lock-count. Before other
threads may access the protected data the recursive mutex must be unlocked
again that number of times. Moreover, the classes 
        hi(timed_mutex)tt(std::timed_mutex) 
    and 
        hi(recursive_timed_mutex)tt(std::recursive_timed_mutex) 
    are available. Their locks expire when released, but also after a certain
amount of time.

The members of the mutex classes perform emi(atomic actions): no context
switch occurs while they are active. So when two threads are trying to
em(lock) a mutex only one can succeed. In the above example: if both threads
would use a mutex to control access to tt(var) thread 2 would not have been
able to assign 12 to tt(var), with thread 1 assuming that its value was 10. We
could even have two threads running purely parallel (e.g., on two separate
cores). E.g.:
        verb(-------------------------------------------------------------------------
Time step:    Thread 1:        Thread 2:        escription
-------------------------------------------------------------------------
    1         starts           starts           T1 and T2 active
    2         locks            locks            Both threads try to 
                                                lock the mutex
    3         blocks...        obtains lock     T2 obtains the lock,
                                                and T1 must wait
    4         (blocked)        processes var    T2 processes var,
                                                T1 still blocked
    5         obtains lock     releases lock    T2 releases the lock,
                                                and T1 immediately 
                                                obtains the lock
    6         processes var                     now T1 processes var
    7         releases lock                     T1 also releases the lock
-------------------------------------------------------------------------)

Although mutexes can directly be used in programs, this rarely happens. It is
more common to embed mutex handling in locking classes that make sure that the
mutex is automatically unlocked again when the mutex lock is no longer
needed. Therefore, this section merely offers an overview of the interfaces of
the mutex classes. Examples of their use will be given in the upcoming
sections (e.g., section ref(LOCKS)).

All mutex classes offer the following constructors and members:
    itemization(
    ittq(mutex() constexpr)
        (The default tt(constexpr) constructor is the only available
constructor;) 

    ittq(~mutex())
        (The destructor does em(not) unlock a locked mutex. If locked it must
explicitly be unlocked using the mutex's tt(unlock) member;)

    ithtq(lock)(void lock())
       (The calling thread blocks until it owns the mutex. Unless tt(lock) is
called for a recursive mutex a em(system_error) is thrown if the thread
already owns the lock. Recursive mutexes increment their internal 
    emi(lock count);)

    ithtq(try_lock)(bool try_lock() noexcept)
       (The calling thread tries to obtain ownership of the mutex. If
ownership is obtained, tt(true) is returned, otherwise tt(false). If the
calling thread already owns the lock tt(true) is also returned, and in this
case a recursive mutex also increments its internal emi(lock count);)

    ithtq(unlock)(void unlock() noexcept)
       (The calling thread releases ownership of the mutex.  A
tt(system_error) is thrown if the thread does not own the
lock. A recursive mutex decrements its interal lock count, releasing
ownership of the mutex once the lock count has decayed to zero;)
    )


The timed-mutex classes (tt(timed_mutex, recursive_timed_mutex)) also offer
these members:
    itemization(
    ithtq(try_lock_for)(bool try_lock_for(chrono::duration<Rep, Period> const 
            &relTime) noexcept)
       (The calling thread tries to obtain ownership of the mutex within the
specified time interval. If ownership is obtained, tt(true) is returned,
otherwise tt(false). If the calling thread already owns the lock tt(true) is
also returned, and in this case a recursive timed mutex also increments its
internal emi(lock count). The tt(Rep) and tt(Duration) types are inferred from
the actual tt(relTime) argument. E.g.,
       verb(std::timed_mutex timedMutex;
timedMutex.try_lock_for(chrono::seconds(5));)

)
        
    ithtq(try_lock_until)(bool try_lock_until(chrono::time_point<Clock,
            Duration> const &absTime) noexcept)
       (The calling thread tries to obtain ownership of the mutex until
tt(absTime) has passed. If ownership is obtained, tt(true) is returned,
otherwise tt(false). If the calling thread already owns the lock tt(true) is
also returned, and in this case a recursive timed mutex also increments its
internal emi(lock count). The tt(Clock) and tt(Duration) types are inferred
from the actual tt(absTime) argument. E.g.,
       verb(std::timed_mutex timedMutex;
timedMutex.try_lock_until(chrono::system_clock::now() + chrono::seconds(5));)

)
    )