1 // Copyright (C) 2010  Davis E. King (davis@dlib.net)
2 // License: Boost Software License   See LICENSE.txt for the full license.
3 #ifndef DLIB_READ_WRITE_MUTEX_EXTENSIOn_
4 #define DLIB_READ_WRITE_MUTEX_EXTENSIOn_
5 
6 #include "threads_kernel.h"
7 #include "read_write_mutex_extension_abstract.h"
8 
9 namespace dlib
10 {
11 
12 // ----------------------------------------------------------------------------------------
13 
14     class read_write_mutex
15     {
16         /*!
17             INITIAL VALUE
18                 - max_locks == defined by constructor
19                 - available_locks == max_locks
20                 - write_lock_in_progress == false
21                 - write_lock_active == false
22 
23             CONVENTION
24                 - Each time someone gets a read only lock they take one of the "available locks"
25                   and each write lock takes all possible locks (i.e. max_locks).  The number of
26                   available locks is recorded in available_locks.  Any time you try to lock this
27                   object and there aren't available locks you have to wait.
28 
29                 - max_locks == max_readonly_locks()
30 
31                 - if (some thread is on the process of obtaining a write lock) then
32                     - write_lock_in_progress == true
33                 - else
34                     - write_lock_in_progress == false
35 
36                 - if (some thread currently has a write lock on this mutex) then
37                     - write_lock_active == true
38                 - else
39                     - write_lock_active == false
40         !*/
41 
42     public:
43 
read_write_mutex()44         read_write_mutex (
45         ) : s(m),
46             max_locks(0xFFFFFFFF),
47             available_locks(max_locks),
48             write_lock_in_progress(false),
49             write_lock_active(false)
50         {}
51 
read_write_mutex(unsigned long max_locks_)52         explicit read_write_mutex (
53             unsigned long max_locks_
54         ) : s(m),
55             max_locks(max_locks_),
56             available_locks(max_locks_),
57             write_lock_in_progress(false),
58             write_lock_active(false)
59         {
60             // make sure requires clause is not broken
61             DLIB_ASSERT(max_locks > 0,
62                 "\t read_write_mutex::read_write_mutex(max_locks)"
63                 << "\n\t You must give a non-zero value for max_locks"
64                 << "\n\t this: " << this
65                 );
66         }
67 
~read_write_mutex()68         ~read_write_mutex (
69         )
70         {}
71 
lock()72         void lock (
73         ) const
74         {
75             m.lock();
76 
77             // If another write lock is already in progress then wait for it to finish
78             // before we start trying to grab all the available locks.  This way we
79             // don't end up fighting over the locks.
80             while (write_lock_in_progress)
81                 s.wait();
82 
83             // grab the right to perform a write lock
84             write_lock_in_progress = true;
85 
86             // now start grabbing all the locks
87             unsigned long locks_obtained = available_locks;
88             available_locks = 0;
89             while (locks_obtained != max_locks)
90             {
91                 s.wait();
92                 locks_obtained += available_locks;
93                 available_locks = 0;
94             }
95 
96             write_lock_in_progress = false;
97             write_lock_active = true;
98 
99             m.unlock();
100         }
101 
unlock()102         void unlock (
103         ) const
104         {
105             m.lock();
106 
107             // only do something if there really was a lock in place
108             if (write_lock_active)
109             {
110                 available_locks = max_locks;
111                 write_lock_active = false;
112                 s.broadcast();
113             }
114 
115             m.unlock();
116         }
117 
lock_readonly()118         void lock_readonly (
119         ) const
120         {
121             m.lock();
122 
123             while (available_locks == 0)
124                 s.wait();
125 
126             --available_locks;
127 
128             m.unlock();
129         }
130 
unlock_readonly()131         void unlock_readonly (
132         ) const
133         {
134             m.lock();
135 
136             // If this condition is false then it means there are no more readonly locks
137             // to free.  So we don't do anything.
138             if (available_locks != max_locks && !write_lock_active)
139             {
140                 ++available_locks;
141 
142                 // only perform broadcast when there is another thread that might be listening
143                 if (available_locks == 1 || write_lock_in_progress)
144                 {
145                     s.broadcast();
146                 }
147             }
148 
149             m.unlock();
150         }
151 
max_readonly_locks()152         unsigned long max_readonly_locks (
153         ) const
154         {
155             return max_locks;
156         }
157 
158     private:
159         mutex m;
160         signaler s;
161         const unsigned long max_locks;
162         mutable unsigned long available_locks;
163         mutable bool write_lock_in_progress;
164         mutable bool write_lock_active;
165 
166         // restricted functions
167         read_write_mutex(read_write_mutex&);        // copy constructor
168         read_write_mutex& operator=(read_write_mutex&);    // assignment operator
169     };
170 
171 // ----------------------------------------------------------------------------------------
172 
173 }
174 
175 #endif // DLIB_READ_WRITE_MUTEX_EXTENSIOn_
176 
177 
178