1 /** 2 * The mutex module provides a primitive for maintaining mutually exclusive 3 * access. 4 * 5 * Copyright: Copyright Sean Kelly 2005 - 2009. 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Authors: Sean Kelly 8 * Source: $(DRUNTIMESRC core/sync/_mutex.d) 9 */ 10 11 /* Copyright Sean Kelly 2005 - 2009. 12 * Distributed under the Boost Software License, Version 1.0. 13 * (See accompanying file LICENSE or copy at 14 * http://www.boost.org/LICENSE_1_0.txt) 15 */ 16 module core.sync.mutex; 17 18 19 public import core.sync.exception; 20 21 import rt.sys.config; 22 23 mixin("import " ~ osMutexImport ~ ";"); 24 25 //////////////////////////////////////////////////////////////////////////////// 26 // Mutex 27 // 28 // void lock(); 29 // void unlock(); 30 // bool tryLock(); 31 //////////////////////////////////////////////////////////////////////////////// 32 33 34 /** 35 * This class represents a general purpose, recursive mutex. 36 * 37 * Implemented using `pthread_mutex` on Posix and `CRITICAL_SECTION` 38 39 * on Windows. 40 */ 41 class Mutex : 42 Object.Monitor 43 { 44 //////////////////////////////////////////////////////////////////////////// 45 // Initialization 46 //////////////////////////////////////////////////////////////////////////// 47 48 49 /** 50 * Initializes a mutex object. 51 * 52 */ 53 this() @trusted nothrow @nogc 54 { 55 this(true); 56 } 57 58 /// ditto 59 this() shared @trusted nothrow @nogc 60 { 61 this(true); 62 } 63 64 // Undocumented, useful only in Mutex.this(). 65 private this(this Q)(bool _unused_) @trusted nothrow @nogc 66 if (is(Q == Mutex) || is(Q == shared Mutex)) 67 { 68 (cast(OsMutex)osMutex).create(); 69 70 m_proxy.link = this; 71 this.__monitor = cast(void*) &m_proxy; 72 } 73 74 75 /** 76 * Initializes a mutex object and sets it as the monitor for `obj`. 77 * 78 * In: 79 * `obj` must not already have a monitor. 80 */ 81 this(Object obj) @trusted nothrow @nogc 82 { 83 this(obj, true); 84 } 85 86 /// ditto 87 this(Object obj) shared @trusted nothrow @nogc 88 { 89 this(obj, true); 90 } 91 92 // Undocumented, useful only in Mutex.this(Object). 93 private this(this Q)(Object obj, bool _unused_) @trusted nothrow @nogc 94 if (is(Q == Mutex) || is(Q == shared Mutex)) 95 in 96 { 97 assert(obj !is null, 98 "The provided object must not be null."); 99 assert(obj.__monitor is null, 100 "The provided object has a monitor already set!"); 101 } 102 do 103 { 104 this(); 105 obj.__monitor = cast(void*) &m_proxy; 106 } 107 108 109 ~this() @trusted nothrow @nogc 110 { 111 (cast(OsMutex)osMutex).destroy(); 112 113 this.__monitor = null; 114 } 115 116 117 //////////////////////////////////////////////////////////////////////////// 118 // General Actions 119 //////////////////////////////////////////////////////////////////////////// 120 121 122 /** 123 * If this lock is not already held by the caller, the lock is acquired, 124 * then the internal counter is incremented by one. 125 * 126 * Note: 127 * `Mutex.lock` does not throw, but a class derived from Mutex can throw. 128 * Use `lock_nothrow` in `nothrow @nogc` code. 129 */ 130 @trusted void lock() 131 { 132 lock_nothrow(); 133 } 134 135 /// ditto 136 @trusted void lock() shared 137 { 138 lock_nothrow(); 139 } 140 141 /// ditto 142 final void lock_nothrow(this Q)() nothrow @trusted @nogc 143 if (is(Q == Mutex) || is(Q == shared Mutex)) 144 { 145 (cast(OsMutex)osMutex).lockNoThrow(); 146 } 147 148 /** 149 * Decrements the internal lock count by one. If this brings the count to 150 * zero, the lock is released. 151 * 152 * Note: 153 * `Mutex.unlock` does not throw, but a class derived from Mutex can throw. 154 * Use `unlock_nothrow` in `nothrow @nogc` code. 155 */ 156 @trusted void unlock() 157 { 158 unlock_nothrow(); 159 } 160 161 /// ditto 162 @trusted void unlock() shared 163 { 164 unlock_nothrow(); 165 } 166 167 /// ditto 168 final void unlock_nothrow(this Q)() nothrow @trusted @nogc 169 if (is(Q == Mutex) || is(Q == shared Mutex)) 170 { 171 (cast(OsMutex)osMutex).unlockNoThrow(); 172 } 173 174 /** 175 * If the lock is held by another caller, the method returns. Otherwise, 176 * the lock is acquired if it is not already held, and then the internal 177 * counter is incremented by one. 178 * 179 * Returns: 180 * true if the lock was acquired and false if not. 181 * 182 * Note: 183 * `Mutex.tryLock` does not throw, but a class derived from Mutex can throw. 184 * Use `tryLock_nothrow` in `nothrow @nogc` code. 185 */ 186 bool tryLock() @trusted 187 { 188 return tryLock_nothrow(); 189 } 190 191 /// ditto 192 bool tryLock() shared @trusted 193 { 194 return tryLock_nothrow(); 195 } 196 197 /// ditto 198 final bool tryLock_nothrow(this Q)() nothrow @trusted @nogc 199 if (is(Q == Mutex) || is(Q == shared Mutex)) 200 { 201 return (cast(OsMutex)osMutex).tryLockNoThrow(); 202 } 203 204 OsMutex osMutex; 205 206 private: 207 208 struct MonitorProxy 209 { 210 Object.Monitor link; 211 } 212 213 MonitorProxy m_proxy; 214 } 215 216 /// 217 /* @safe nothrow -> see druntime PR 1726 */ 218 // Test regular usage. 219 unittest 220 { 221 import core.thread : Thread; 222 223 class Resource 224 { 225 Mutex mtx; 226 int cargo; 227 228 this() shared @safe nothrow 229 { 230 mtx = new shared Mutex(); 231 cargo = 42; 232 } 233 234 void useResource() shared @safe nothrow @nogc 235 { 236 mtx.lock_nothrow(); 237 (cast() cargo) += 1; 238 mtx.unlock_nothrow(); 239 } 240 } 241 242 shared Resource res = new shared Resource(); 243 244 auto otherThread = new Thread( 245 { 246 foreach (i; 0 .. 10000) 247 res.useResource(); 248 }).start(); 249 250 foreach (i; 0 .. 10000) 251 res.useResource(); 252 253 otherThread.join(); 254 255 assert (res.cargo == 20042); 256 } 257 258 // Test @nogc usage. 259 @system @nogc nothrow unittest 260 { 261 import core.stdc.stdlib : malloc, free; 262 import core.lifetime : emplace; 263 264 auto mtx = cast(shared Mutex) malloc(__traits(classInstanceSize, Mutex)); 265 emplace(mtx); 266 267 mtx.lock_nothrow(); 268 269 { // test recursive locking 270 mtx.tryLock_nothrow(); 271 mtx.unlock_nothrow(); 272 } 273 274 mtx.unlock_nothrow(); 275 276 // In general destorying classes like this is not 277 // safe, but since we know that the only base class 278 // of Mutex is Object and it doesn't have a dtor 279 // we can simply call the non-virtual __dtor() here. 280 281 // Ok to cast away shared because destruction 282 // should happen only from a single thread. 283 (cast(Mutex) mtx).__dtor(); 284 285 // Verify that the underlying implementation has been destroyed by checking 286 // that locking is not possible. This assumes that the underlying 287 // implementation is well behaved and makes the object non-lockable upon 288 // destruction. The Bionic, DragonFly, Musl, and Solaris C runtimes don't 289 // appear to do so, so skip this test. 290 version (CRuntime_Bionic) {} else 291 version (CRuntime_Musl) {} else 292 version (DragonFlyBSD) {} else 293 version (Solaris) {} else 294 assert(!mtx.tryLock_nothrow()); 295 296 free(cast(void*) mtx); 297 } 298 299 // Test single-thread (non-shared) use. 300 unittest 301 { 302 Mutex m = new Mutex(); 303 304 m.lock(); 305 306 m.tryLock(); 307 m.unlock(); 308 309 m.unlock(); 310 } 311 312 unittest 313 { 314 import core.thread; 315 316 auto mutex = new Mutex; 317 int numThreads = 10; 318 int numTries = 1000; 319 int lockCount = 0; 320 321 void testFn() 322 { 323 for (int i = 0; i < numTries; ++i) 324 { 325 synchronized (mutex) 326 { 327 ++lockCount; 328 } 329 } 330 } 331 332 auto group = new ThreadGroup; 333 334 for (int i = 0; i < numThreads; ++i) 335 group.create(&testFn); 336 337 group.joinAll(); 338 assert(lockCount == numThreads * numTries); 339 }