Class: QuackConcurrency::Mutex

Inherits:
Object
  • Object
show all
Defined in:
lib/quack_concurrency/mutex.rb

Overview

Mutex is similar to ::Mutex.

A few differences include:

  • #lock supports passing a block and behaves like ::Mutex#synchronize

  • #unlock supports passing a block

Direct Known Subclasses

ReentrantMutex

Instance Method Summary collapse

Constructor Details

#initializeMutex

Creates a new QuackConcurrency::Mutex concurrency tool.



12
13
14
15
16
# File 'lib/quack_concurrency/mutex.rb', line 12

def initialize
  @condition_variable = SafeConditionVariable.new
  @mutex = ::Mutex.new
  @owner = nil
end

Instance Method Details

#lockvoid #lock { ... } ⇒ Object

Overloads:

  • #lockvoid

    This method returns an undefined value.

    Obtains the lock or sleeps the current thread until it is available.

  • #lock { ... } ⇒ Object

    Obtains the lock, runs the block, then releases the lock when the block completes.

    Yields:

    • block to run while holding the lock

    Returns:

    • (Object)

      result of the block

    Raises:

    • (Exception)

      any exception raised in block

Raises:

  • (ThreadError)

    if current thread is already locking it



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/quack_concurrency/mutex.rb', line 27

def lock(&block)
  raise ThreadError, 'Attempt to lock a mutex which is already locked by this thread' if owned?
  if block_given?
    lock
    begin
      yield
    ensure
      unlock
    end
  else
    @mutex.synchronize do
      @condition_variable.wait(@mutex) if locked?
      @owner = caller
    end
    nil
  end
end

#locked?Boolean

Checks if it is locked by a thread.

Returns:

  • (Boolean)


47
48
49
# File 'lib/quack_concurrency/mutex.rb', line 47

def locked?
  !!@owner
end

#locked_out?Boolean

Checks if it is locked by another thread.

Returns:

  • (Boolean)


53
54
55
56
# File 'lib/quack_concurrency/mutex.rb', line 53

def locked_out?
  # don't need a mutex because we know #owned? can't change during the call 
  locked? && !owned?
end

#owned?Boolean

Checks if it is locked by current thread.

Returns:

  • (Boolean)


60
61
62
# File 'lib/quack_concurrency/mutex.rb', line 60

def owned?
  @owner == caller
end

#ownernil, Thread

Returns the thread locking it if one exists.

Returns:

  • (nil, Thread)

    the locking Thread if one exists, otherwise nil



66
67
68
# File 'lib/quack_concurrency/mutex.rb', line 66

def owner
  @owner
end

#sleep(timeout = nil) ⇒ Integer

Releases the lock and puts this thread to sleep.

Parameters:

  • timeout (nil, Numeric) (defaults to: nil)

    time to sleep in seconds or nil to sleep forever

Returns:

  • (Integer)

    elapsed time sleeping

Raises:

  • (TypeError)

    if timeout is not nil or Numeric

  • (ArgumentError)

    if timeout is not positive



75
76
77
78
79
80
81
82
83
84
# File 'lib/quack_concurrency/mutex.rb', line 75

def sleep(timeout = nil)
  validate_timeout(timeout)
  unlock do
    if timeout == nil || timeout == Float::INFINITY
      elapsed_time = (timer { Thread.stop }).round
    else
      elapsed_time = Kernel.sleep(timeout)
    end
  end
end

#synchronize(&block) ⇒ Object

Obtains the lock or blocks until the lock is available.

Returns:

  • (Object)

    value return from block

Raises:

  • (ThreadError)

    if block not given

  • (ThreadError)

    if current thread is already locking it

  • (Exception)

    any exception raised in block



91
92
93
94
# File 'lib/quack_concurrency/mutex.rb', line 91

def synchronize(&block)
  raise ThreadError, 'must be called with a block' unless block_given?
  lock(&block)
end

#try_lockBoolean

Attempts to obtain the lock and return immediately.

Returns:

  • (Boolean)

    returns if the lock was granted

Raises:

  • (ThreadError)

    if current thread is already locking it



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/quack_concurrency/mutex.rb', line 99

def try_lock
  raise ThreadError, 'Attempt to lock a mutex which is already locked by this thread' if owned?
  @mutex.synchronize do
    if locked?
      false
    else
      @owner = caller
      true
    end
  end
end

#unlockvoid #unlock { ... } ⇒ Object

Overloads:

  • #unlockvoid

    This method returns an undefined value.

    Releases the lock

  • #unlock { ... } ⇒ Object

    Releases the lock, runs the block, then reacquires the lock when available,

    blocking if necessary.
    

    Yields:

    • block to run while releasing the lock

    Returns:

    • (Object)

      result of the block

    Raises:

    • (Exception)

      any exception raised in block

Raises:

  • (ThreadError)

    if current thread is not locking it



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/quack_concurrency/mutex.rb', line 121

def unlock(&block)
  if block_given?
    temporarily_release(&block)
  else
    @mutex.synchronize do
      ensure_can_unlock
      if @condition_variable.any_waiting_threads?
        @condition_variable.signal
        
        # we do this to avoid a bug
        # consider this problem, imagine we have three threads:
        #   * A: this thread
        #   * B: has previously called #lock and is waiting on the @condition_variable
        #   * C: enters #lock after A has released the lock but before B has reacquired it
        #   is this scenario the threads may end up executing not in the chronological order
        #     that they entered #lock
        @owner = true
      else
        @owner = nil
      end
    end
    nil
  end
end

#waiting_threads_countInteger

Returns the number of threads currently waiting on it.

Returns:

  • (Integer)


148
149
150
# File 'lib/quack_concurrency/mutex.rb', line 148

def waiting_threads_count
  @condition_variable.waiting_threads_count
end