concurrent-ruby 0.4.1 → 0.5.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,125 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
module MutexAtomicFixnum # :nodoc:
|
5
|
+
|
6
|
+
# @!visibility private
|
7
|
+
def allocate_storage(init) # :nodoc:
|
8
|
+
@value = init
|
9
|
+
@mutex = Mutex.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
@mutex.synchronize do
|
14
|
+
@value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def value=(value)
|
19
|
+
raise ArgumentError.new('value must be a Fixnum') unless value.is_a?(Fixnum)
|
20
|
+
@mutex.synchronize do
|
21
|
+
@value = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def increment
|
26
|
+
@mutex.synchronize do
|
27
|
+
@value += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def decrement
|
32
|
+
@mutex.synchronize do
|
33
|
+
@value -= 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def compare_and_set(expect, update) # :nodoc:
|
39
|
+
@mutex.synchronize do
|
40
|
+
if @value == expect
|
41
|
+
@value = update
|
42
|
+
true
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!visibility private
|
51
|
+
module JavaAtomicFixnum # :nodoc:
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def allocate_storage(init) # :nodoc:
|
55
|
+
@atomic = java.util.concurrent.atomic.AtomicLong.new(init)
|
56
|
+
end
|
57
|
+
|
58
|
+
def value
|
59
|
+
@atomic.get
|
60
|
+
end
|
61
|
+
|
62
|
+
def value=(value)
|
63
|
+
raise ArgumentError.new('value must be a Fixnum') unless value.is_a?(Fixnum)
|
64
|
+
@atomic.set(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def increment
|
68
|
+
@atomic.increment_and_get
|
69
|
+
end
|
70
|
+
|
71
|
+
def decrement
|
72
|
+
@atomic.decrement_and_get
|
73
|
+
end
|
74
|
+
|
75
|
+
# @!visibility private
|
76
|
+
def compare_and_set(expect, update) # :nodoc:
|
77
|
+
@atomic.compare_and_set(expect, update)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A numeric value that can be updated atomically. Reads and writes to an atomic
|
82
|
+
# fixnum and thread-safe and guaranteed to succeed. Reads and writes may block
|
83
|
+
# briefly but no explicit locking is required.
|
84
|
+
#
|
85
|
+
# @!method value()
|
86
|
+
# Retrieves the current +Fixnum+ value
|
87
|
+
# @return [Fixnum] the current value
|
88
|
+
#
|
89
|
+
# @!method value=(value)
|
90
|
+
# Explicitly sets the value
|
91
|
+
# @param [Fixnum] value the new value to be set
|
92
|
+
# @return [Fixnum] the current value
|
93
|
+
# @raise [ArgumentError] if the new value is not a +Fixnum+
|
94
|
+
#
|
95
|
+
# @!method increment()
|
96
|
+
# Increases the current value by 1
|
97
|
+
# @return [Fixnum] the current value after incrementation
|
98
|
+
#
|
99
|
+
# @!method decrement()
|
100
|
+
# Decreases the current value by 1
|
101
|
+
# @return [Fixnum] the current value after decrementation
|
102
|
+
#
|
103
|
+
# @since 0.5.0
|
104
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html java.util.concurrent.atomic.AtomicLong
|
105
|
+
class AtomicFixnum
|
106
|
+
|
107
|
+
# Creates a new +AtomicFixnum+ with the given initial value.
|
108
|
+
#
|
109
|
+
# @param [Fixnum] init the initial value
|
110
|
+
# @raise [ArgumentError] if the initial value is not a +Fixnum+
|
111
|
+
def initialize(init = 0)
|
112
|
+
raise ArgumentError.new('initial value must be a Fixnum') unless init.is_a?(Fixnum)
|
113
|
+
allocate_storage(init)
|
114
|
+
end
|
115
|
+
|
116
|
+
if defined? java.util
|
117
|
+
include JavaAtomicFixnum
|
118
|
+
else
|
119
|
+
include MutexAtomicFixnum
|
120
|
+
end
|
121
|
+
|
122
|
+
alias_method :up, :increment
|
123
|
+
alias_method :down, :decrement
|
124
|
+
end
|
125
|
+
end
|
data/lib/concurrent/channel.rb
CHANGED
@@ -3,9 +3,44 @@ require 'concurrent/stoppable'
|
|
3
3
|
|
4
4
|
module Concurrent
|
5
5
|
|
6
|
+
# +Channel+ is a functional programming variation of +Actor+, based very loosely on the
|
7
|
+
# *MailboxProcessor* agent in F#. +Actor+ is used to create objects that receive messages
|
8
|
+
# from other threads then processes those messages based on the behavior of the class.
|
9
|
+
# +Channel+ creates objects that receive messages and processe them using the block
|
10
|
+
# given at construction. +Channel+ is implemented as a subclass of +Actor+ and supports
|
11
|
+
# all message-passing methods of that class. +Channel+ also supports pools with a shared
|
12
|
+
# mailbox.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# channel = Concurrent::Channel.new do |msg|
|
16
|
+
# sleep(1)
|
17
|
+
# puts "#{msg}\n"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# channel.run! => #<Thread:0x007fa123d95fc8 sleep>
|
21
|
+
#
|
22
|
+
# channel.post("Hello, World!") => 1
|
23
|
+
# # wait...
|
24
|
+
# => Hello, World!
|
25
|
+
#
|
26
|
+
# future = channel.post? "Don't Panic." => #<Concurrent::IVar:0x007fa123d6d9d8 @state=:pending...
|
27
|
+
# future.pending? => true
|
28
|
+
# # wait...
|
29
|
+
# => "Don't Panic."
|
30
|
+
# future.fulfilled? => true
|
31
|
+
#
|
32
|
+
# channel.stop => true
|
33
|
+
#
|
34
|
+
# @see http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx Async and Parallel Design Patterns in F#: Agents
|
35
|
+
# @see http://msdn.microsoft.com/en-us/library/ee370357.aspx Control.MailboxProcessor<'Msg> Class (F#)
|
6
36
|
class Channel < Actor
|
7
37
|
include Stoppable
|
8
38
|
|
39
|
+
# Initialize a new object with a block operation to be performed in response
|
40
|
+
# to every received message.
|
41
|
+
#
|
42
|
+
# @yield [message] Removes the next message from the queue and processes it
|
43
|
+
# @yieldparam [Array] msg The next message post to the channel
|
9
44
|
def initialize(&block)
|
10
45
|
raise ArgumentError.new('no block given') unless block_given?
|
11
46
|
super()
|
@@ -21,7 +56,7 @@ module Concurrent
|
|
21
56
|
|
22
57
|
private
|
23
58
|
|
24
|
-
def act(*message)
|
59
|
+
def act(*message) # :nodoc:
|
25
60
|
return @task.call(*message)
|
26
61
|
end
|
27
62
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# Condition is a better implementation of standard Ruby ConditionVariable.
|
4
|
+
# The biggest difference is the wait return value: Condition#wait returns
|
5
|
+
# Condition::Result which make possible to know if waiting thread has been woken up
|
6
|
+
# by an another thread (using #signal or #broadcast) or due to timeout.
|
7
|
+
#
|
8
|
+
# Every #wait must be guarded by a locked Mutex or a ThreadError will be risen.
|
9
|
+
# Although it's not mandatory, it's recommended to call also #signal and #broadcast within
|
10
|
+
# the same mutex
|
11
|
+
class Condition
|
12
|
+
|
13
|
+
class Result
|
14
|
+
def initialize(remaining_time)
|
15
|
+
@remaining_time = remaining_time
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :remaining_time
|
19
|
+
|
20
|
+
# @return [Boolean] true if current thread has been waken up by a #signal or a #broadcast call, otherwise false
|
21
|
+
def woken_up?
|
22
|
+
@remaining_time.nil? || @remaining_time > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if current thread has been waken up due to a timeout, otherwise false
|
26
|
+
def timed_out?
|
27
|
+
@remaining_time != nil && @remaining_time <= 0
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :can_wait?, :woken_up?
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@condition = ConditionVariable.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Mutex] mutex the locked mutex guarding the wait
|
39
|
+
# @param [Object] timeout nil means no timeout
|
40
|
+
# @return [Result]
|
41
|
+
def wait(mutex, timeout = nil)
|
42
|
+
start_time = Time.now.to_f
|
43
|
+
@condition.wait(mutex, timeout)
|
44
|
+
|
45
|
+
if timeout.nil?
|
46
|
+
Result.new(nil)
|
47
|
+
else
|
48
|
+
Result.new(start_time + timeout - Time.now.to_f)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Wakes up a waiting thread
|
53
|
+
# @return [true]
|
54
|
+
def signal
|
55
|
+
@condition.signal
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
# Wakes up all waiting threads
|
60
|
+
# @return [true]
|
61
|
+
def broadcast
|
62
|
+
@condition.broadcast
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A thread safe observer set implemented using copy-on-read approach:
|
4
|
+
# observers are added and removed from a thread safe collection; every time
|
5
|
+
# a notification is required the internal data structure is copied to
|
6
|
+
# prevent concurrency issues
|
7
|
+
class CopyOnNotifyObserverSet
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@observers = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds an observer to this set
|
15
|
+
# @param [Object] observer the observer to add
|
16
|
+
# @param [Symbol] func the function to call on the observer during notification. Default is :update
|
17
|
+
# @return [Symbol] the added function
|
18
|
+
def add_observer(observer, func=:update)
|
19
|
+
@mutex.synchronize { @observers[observer] = func }
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Object] observer the observer to remove
|
23
|
+
# @return [Object] the deleted observer
|
24
|
+
def delete_observer(observer)
|
25
|
+
@mutex.synchronize { @observers.delete(observer) }
|
26
|
+
observer
|
27
|
+
end
|
28
|
+
|
29
|
+
# Deletes all observers
|
30
|
+
# @return [CopyOnWriteObserverSet] self
|
31
|
+
def delete_observers
|
32
|
+
@mutex.synchronize { @observers.clear }
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Integer] the observers count
|
37
|
+
def count_observers
|
38
|
+
@mutex.synchronize { @observers.count }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Notifies all registered observers with optional args
|
42
|
+
# @param [Object] args arguments to be passed to each observer
|
43
|
+
# @return [CopyOnWriteObserverSet] self
|
44
|
+
def notify_observers(*args, &block)
|
45
|
+
observers = @mutex.synchronize { @observers.dup }
|
46
|
+
notify_to(observers, *args, &block)
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Notifies all registered observers with optional args and deletes them.
|
52
|
+
#
|
53
|
+
# @param [Object] args arguments to be passed to each observer
|
54
|
+
# @return [CopyOnWriteObserverSet] self
|
55
|
+
def notify_and_delete_observers(*args, &block)
|
56
|
+
observers = duplicate_and_clear_observers
|
57
|
+
notify_to(observers, *args, &block)
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def duplicate_and_clear_observers
|
65
|
+
@mutex.synchronize do
|
66
|
+
observers = @observers.dup
|
67
|
+
@observers.clear
|
68
|
+
observers
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def notify_to(observers, *args)
|
73
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && ! args.empty?
|
74
|
+
observers.each do |observer, function|
|
75
|
+
args = yield if block_given?
|
76
|
+
observer.send(function, *args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A thread safe observer set implemented using copy-on-write approach:
|
4
|
+
# every time an observer is added or removed the whole internal data structure is
|
5
|
+
# duplicated and replaced with a new one.
|
6
|
+
class CopyOnWriteObserverSet
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mutex = Mutex.new
|
10
|
+
@observers = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Adds an observer to this set
|
14
|
+
# @param [Object] observer the observer to add
|
15
|
+
# @param [Symbol] func the function to call on the observer during notification. Default is :update
|
16
|
+
# @return [Symbol] the added function
|
17
|
+
def add_observer(observer, func=:update)
|
18
|
+
@mutex.synchronize do
|
19
|
+
new_observers = @observers.dup
|
20
|
+
new_observers[observer] = func
|
21
|
+
@observers = new_observers
|
22
|
+
end
|
23
|
+
func
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Object] observer the observer to remove
|
27
|
+
# @return [Object] the deleted observer
|
28
|
+
def delete_observer(observer)
|
29
|
+
@mutex.synchronize do
|
30
|
+
new_observers = @observers.dup
|
31
|
+
new_observers.delete(observer)
|
32
|
+
@observers = new_observers
|
33
|
+
end
|
34
|
+
observer
|
35
|
+
end
|
36
|
+
|
37
|
+
# Deletes all observers
|
38
|
+
# @return [CopyOnWriteObserverSet] self
|
39
|
+
def delete_observers
|
40
|
+
self.observers = {}
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# @return [Integer] the observers count
|
46
|
+
def count_observers
|
47
|
+
observers.count
|
48
|
+
end
|
49
|
+
|
50
|
+
# Notifies all registered observers with optional args
|
51
|
+
# @param [Object] args arguments to be passed to each observer
|
52
|
+
# @return [CopyOnWriteObserverSet] self
|
53
|
+
def notify_observers(*args, &block)
|
54
|
+
notify_to(observers, *args, &block)
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Notifies all registered observers with optional args and deletes them.
|
59
|
+
#
|
60
|
+
# @param [Object] args arguments to be passed to each observer
|
61
|
+
# @return [CopyOnWriteObserverSet] self
|
62
|
+
def notify_and_delete_observers(*args, &block)
|
63
|
+
old = clear_observers_and_return_old
|
64
|
+
notify_to(old, *args, &block)
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def notify_to(observers, *args)
|
71
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && ! args.empty?
|
72
|
+
observers.each do |observer, function|
|
73
|
+
args = yield if block_given?
|
74
|
+
observer.send(function, *args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def observers
|
79
|
+
@mutex.synchronize { @observers }
|
80
|
+
end
|
81
|
+
|
82
|
+
def observers=(new_set)
|
83
|
+
@mutex.synchronize { @observers = new_set}
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear_observers_and_return_old
|
87
|
+
@mutex.synchronize do
|
88
|
+
old_observers = @observers
|
89
|
+
@observers = {}
|
90
|
+
old_observers
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
# A synchronization object that allows one thread to wait on multiple other threads.
|
4
|
+
# The thread that will wait creates a +CountDownLatch+ and sets the initial value
|
5
|
+
# (normally equal to the number of other threads). The initiating thread passes the
|
6
|
+
# latch to the other threads then waits for the other threads by calling the +#wait+
|
7
|
+
# method. Each of the other threads calls +#count_down+ when done with its work.
|
8
|
+
# When the latch counter reaches zero the waiting thread is unblocked and continues
|
9
|
+
# with its work. A +CountDownLatch+ can be used only once. Its value cannot be reset.
|
10
|
+
class CountDownLatch
|
11
|
+
|
12
|
+
# Create a new +CountDownLatch+ with the initial +count+.
|
13
|
+
#
|
14
|
+
# @param [Fixnum] count the initial count
|
15
|
+
#
|
16
|
+
# @raise [ArgumentError] if +count+ is not an integer or is less than zero
|
17
|
+
def initialize(count)
|
18
|
+
unless count.is_a?(Fixnum) && count >= 0
|
19
|
+
raise ArgumentError.new('count must be in integer greater than or equal zero')
|
20
|
+
end
|
21
|
+
@mutex = Mutex.new
|
22
|
+
@condition = Condition.new
|
23
|
+
@count = count
|
24
|
+
end
|
25
|
+
|
26
|
+
# Block on the latch until the counter reaches zero or until +timeout+ is reached.
|
27
|
+
#
|
28
|
+
# @param [Fixnum] timeout the number of seconds to wait for the counter or +nil+
|
29
|
+
# to block indefinitely
|
30
|
+
# @return [Boolean] +true+ if the +count+ reaches zero else false on +timeout+
|
31
|
+
def wait(timeout = nil)
|
32
|
+
@mutex.synchronize do
|
33
|
+
|
34
|
+
remaining = Condition::Result.new(timeout)
|
35
|
+
while @count > 0 && remaining.can_wait?
|
36
|
+
remaining = @condition.wait(@mutex, remaining.remaining_time)
|
37
|
+
end
|
38
|
+
|
39
|
+
@count == 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Signal the latch to decrement the counter. Will signal all blocked threads when
|
44
|
+
# the +count+ reaches zero.
|
45
|
+
def count_down
|
46
|
+
@mutex.synchronize do
|
47
|
+
@count -= 1 if @count > 0
|
48
|
+
@condition.broadcast if @count == 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# The current value of the counter.
|
53
|
+
#
|
54
|
+
# @return [Fixnum] the current value of the counter
|
55
|
+
def count
|
56
|
+
@mutex.synchronize { @count }
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|