concurrent-ruby 0.4.1 → 0.5.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|