| /** |
| * The mutex module provides a primitive for maintaining mutually exclusive |
| * access. |
| * |
| * Copyright: Copyright Sean Kelly 2005 - 2009. |
| * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Authors: Sean Kelly |
| * Source: $(DRUNTIMESRC core/sync/_mutex.d) |
| */ |
| |
| /* Copyright Sean Kelly 2005 - 2009. |
| * Distributed under the Boost Software License, Version 1.0. |
| * (See accompanying file LICENSE or copy at |
| * http://www.boost.org/LICENSE_1_0.txt) |
| */ |
| module core.sync.mutex; |
| |
| |
| public import core.sync.exception; |
| |
| version (Windows) |
| { |
| private import core.sys.windows.winbase /+: CRITICAL_SECTION, DeleteCriticalSection, |
| EnterCriticalSection, InitializeCriticalSection, LeaveCriticalSection, |
| TryEnterCriticalSection+/; |
| } |
| else version (Posix) |
| { |
| private import core.sys.posix.pthread; |
| } |
| else |
| { |
| static assert(false, "Platform not supported"); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Mutex |
| // |
| // void lock(); |
| // void unlock(); |
| // bool tryLock(); |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| |
| /** |
| * This class represents a general purpose, recursive mutex. |
| * |
| * Implemented using `pthread_mutex` on Posix and `CRITICAL_SECTION` |
| * on Windows. |
| */ |
| class Mutex : |
| Object.Monitor |
| { |
| //////////////////////////////////////////////////////////////////////////// |
| // Initialization |
| //////////////////////////////////////////////////////////////////////////// |
| |
| |
| /** |
| * Initializes a mutex object. |
| * |
| */ |
| this() @trusted nothrow @nogc |
| { |
| this(true); |
| } |
| |
| /// ditto |
| this() shared @trusted nothrow @nogc |
| { |
| this(true); |
| } |
| |
| // Undocumented, useful only in Mutex.this(). |
| private this(this Q)(bool _unused_) @trusted nothrow @nogc |
| if (is(Q == Mutex) || is(Q == shared Mutex)) |
| { |
| version (Windows) |
| { |
| InitializeCriticalSection(cast(CRITICAL_SECTION*) &m_hndl); |
| } |
| else version (Posix) |
| { |
| import core.internal.abort : abort; |
| pthread_mutexattr_t attr = void; |
| |
| !pthread_mutexattr_init(&attr) || |
| abort("Error: pthread_mutexattr_init failed."); |
| |
| scope (exit) !pthread_mutexattr_destroy(&attr) || |
| abort("Error: pthread_mutexattr_destroy failed."); |
| |
| !pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) || |
| abort("Error: pthread_mutexattr_settype failed."); |
| |
| !pthread_mutex_init(cast(pthread_mutex_t*) &m_hndl, &attr) || |
| abort("Error: pthread_mutex_init failed."); |
| } |
| |
| m_proxy.link = this; |
| this.__monitor = cast(void*) &m_proxy; |
| } |
| |
| |
| /** |
| * Initializes a mutex object and sets it as the monitor for `obj`. |
| * |
| * In: |
| * `obj` must not already have a monitor. |
| */ |
| this(Object obj) @trusted nothrow @nogc |
| { |
| this(obj, true); |
| } |
| |
| /// ditto |
| this(Object obj) shared @trusted nothrow @nogc |
| { |
| this(obj, true); |
| } |
| |
| // Undocumented, useful only in Mutex.this(Object). |
| private this(this Q)(Object obj, bool _unused_) @trusted nothrow @nogc |
| if (is(Q == Mutex) || is(Q == shared Mutex)) |
| in |
| { |
| assert(obj !is null, |
| "The provided object must not be null."); |
| assert(obj.__monitor is null, |
| "The provided object has a monitor already set!"); |
| } |
| body |
| { |
| this(); |
| obj.__monitor = cast(void*) &m_proxy; |
| } |
| |
| |
| ~this() @trusted nothrow @nogc |
| { |
| version (Windows) |
| { |
| DeleteCriticalSection(&m_hndl); |
| } |
| else version (Posix) |
| { |
| import core.internal.abort : abort; |
| !pthread_mutex_destroy(&m_hndl) || |
| abort("Error: pthread_mutex_destroy failed."); |
| } |
| this.__monitor = null; |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // General Actions |
| //////////////////////////////////////////////////////////////////////////// |
| |
| |
| /** |
| * If this lock is not already held by the caller, the lock is acquired, |
| * then the internal counter is incremented by one. |
| * |
| * Note: |
| * `Mutex.lock` does not throw, but a class derived from Mutex can throw. |
| * Use `lock_nothrow` in `nothrow @nogc` code. |
| */ |
| @trusted void lock() |
| { |
| lock_nothrow(); |
| } |
| |
| /// ditto |
| @trusted void lock() shared |
| { |
| lock_nothrow(); |
| } |
| |
| /// ditto |
| final void lock_nothrow(this Q)() nothrow @trusted @nogc |
| if (is(Q == Mutex) || is(Q == shared Mutex)) |
| { |
| version (Windows) |
| { |
| EnterCriticalSection(&m_hndl); |
| } |
| else version (Posix) |
| { |
| if (pthread_mutex_lock(&m_hndl) == 0) |
| return; |
| |
| SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer; |
| syncErr.msg = "Unable to lock mutex."; |
| throw syncErr; |
| } |
| } |
| |
| /** |
| * Decrements the internal lock count by one. If this brings the count to |
| * zero, the lock is released. |
| * |
| * Note: |
| * `Mutex.unlock` does not throw, but a class derived from Mutex can throw. |
| * Use `unlock_nothrow` in `nothrow @nogc` code. |
| */ |
| @trusted void unlock() |
| { |
| unlock_nothrow(); |
| } |
| |
| /// ditto |
| @trusted void unlock() shared |
| { |
| unlock_nothrow(); |
| } |
| |
| /// ditto |
| final void unlock_nothrow(this Q)() nothrow @trusted @nogc |
| if (is(Q == Mutex) || is(Q == shared Mutex)) |
| { |
| version (Windows) |
| { |
| LeaveCriticalSection(&m_hndl); |
| } |
| else version (Posix) |
| { |
| if (pthread_mutex_unlock(&m_hndl) == 0) |
| return; |
| |
| SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer; |
| syncErr.msg = "Unable to unlock mutex."; |
| throw syncErr; |
| } |
| } |
| |
| /** |
| * If the lock is held by another caller, the method returns. Otherwise, |
| * the lock is acquired if it is not already held, and then the internal |
| * counter is incremented by one. |
| * |
| * Returns: |
| * true if the lock was acquired and false if not. |
| * |
| * Note: |
| * `Mutex.tryLock` does not throw, but a class derived from Mutex can throw. |
| * Use `tryLock_nothrow` in `nothrow @nogc` code. |
| */ |
| bool tryLock() @trusted |
| { |
| return tryLock_nothrow(); |
| } |
| |
| /// ditto |
| bool tryLock() shared @trusted |
| { |
| return tryLock_nothrow(); |
| } |
| |
| /// ditto |
| final bool tryLock_nothrow(this Q)() nothrow @trusted @nogc |
| if (is(Q == Mutex) || is(Q == shared Mutex)) |
| { |
| version (Windows) |
| { |
| return TryEnterCriticalSection(&m_hndl) != 0; |
| } |
| else version (Posix) |
| { |
| return pthread_mutex_trylock(&m_hndl) == 0; |
| } |
| } |
| |
| |
| private: |
| version (Windows) |
| { |
| CRITICAL_SECTION m_hndl; |
| } |
| else version (Posix) |
| { |
| pthread_mutex_t m_hndl; |
| } |
| |
| struct MonitorProxy |
| { |
| Object.Monitor link; |
| } |
| |
| MonitorProxy m_proxy; |
| |
| |
| package: |
| version (Posix) |
| { |
| pthread_mutex_t* handleAddr() |
| { |
| return &m_hndl; |
| } |
| } |
| } |
| |
| /// |
| /* @safe nothrow -> see druntime PR 1726 */ |
| // Test regular usage. |
| unittest |
| { |
| import core.thread : Thread; |
| |
| class Resource |
| { |
| Mutex mtx; |
| int cargo; |
| |
| this() shared @safe nothrow |
| { |
| mtx = new shared Mutex(); |
| cargo = 42; |
| } |
| |
| void useResource() shared @safe nothrow @nogc |
| { |
| mtx.lock_nothrow(); |
| (cast() cargo) += 1; |
| mtx.unlock_nothrow(); |
| } |
| } |
| |
| shared Resource res = new shared Resource(); |
| |
| auto otherThread = new Thread( |
| { |
| foreach (i; 0 .. 10000) |
| res.useResource(); |
| }).start(); |
| |
| foreach (i; 0 .. 10000) |
| res.useResource(); |
| |
| otherThread.join(); |
| |
| assert (res.cargo == 20042); |
| } |
| |
| // Test @nogc usage. |
| @system @nogc nothrow unittest |
| { |
| import core.stdc.stdlib : malloc, free; |
| |
| void* p = malloc(__traits(classInstanceSize, Mutex)); |
| |
| auto ti = typeid(Mutex); |
| p[0 .. ti.initializer.length] = ti.initializer[]; |
| |
| shared Mutex mtx = cast(shared(Mutex)) p; |
| mtx.__ctor(); |
| |
| mtx.lock_nothrow(); |
| |
| { // test recursive locking |
| mtx.tryLock_nothrow(); |
| mtx.unlock_nothrow(); |
| } |
| |
| mtx.unlock_nothrow(); |
| |
| // In general destorying classes like this is not |
| // safe, but since we know that the only base class |
| // of Mutex is Object and it doesn't have a dtor |
| // we can simply call the non-virtual __dtor() here. |
| |
| // Ok to cast away shared because destruction |
| // should happen only from a single thread. |
| (cast(Mutex) mtx).__dtor(); |
| |
| // Verify that the underlying implementation has been destroyed by checking |
| // that locking is not possible. This assumes that the underlying |
| // implementation is well behaved and makes the object non-lockable upon |
| // destruction. The Bionic, DragonFly, Musl, and Solaris C runtimes don't |
| // appear to do so, so skip this test. |
| version (CRuntime_Bionic) {} else |
| version (CRuntime_Musl) {} else |
| version (DragonFlyBSD) {} else |
| version (Solaris) {} else |
| assert(!mtx.tryLock_nothrow()); |
| |
| free(cast(void*) mtx); |
| } |
| |
| // Test single-thread (non-shared) use. |
| unittest |
| { |
| Mutex m = new Mutex(); |
| |
| m.lock(); |
| |
| m.tryLock(); |
| m.unlock(); |
| |
| m.unlock(); |
| } |
| |
| unittest |
| { |
| import core.thread; |
| |
| auto mutex = new Mutex; |
| int numThreads = 10; |
| int numTries = 1000; |
| int lockCount = 0; |
| |
| void testFn() |
| { |
| for (int i = 0; i < numTries; ++i) |
| { |
| synchronized (mutex) |
| { |
| ++lockCount; |
| } |
| } |
| } |
| |
| auto group = new ThreadGroup; |
| |
| for (int i = 0; i < numThreads; ++i) |
| group.create(&testFn); |
| |
| group.joinAll(); |
| assert(lockCount == numThreads * numTries); |
| } |