This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
require 'concurrent/utility/native_extension_loader' # load native parts first
|
||||
|
||||
require 'concurrent/atomic/mutex_atomic_boolean'
|
||||
|
||||
module Concurrent
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro atomic_boolean_method_initialize
|
||||
#
|
||||
# Creates a new `AtomicBoolean` with the given initial value.
|
||||
#
|
||||
# @param [Boolean] initial the initial value
|
||||
|
||||
# @!macro atomic_boolean_method_value_get
|
||||
#
|
||||
# Retrieves the current `Boolean` value.
|
||||
#
|
||||
# @return [Boolean] the current value
|
||||
|
||||
# @!macro atomic_boolean_method_value_set
|
||||
#
|
||||
# Explicitly sets the value.
|
||||
#
|
||||
# @param [Boolean] value the new value to be set
|
||||
#
|
||||
# @return [Boolean] the current value
|
||||
|
||||
# @!macro atomic_boolean_method_true_question
|
||||
#
|
||||
# Is the current value `true`
|
||||
#
|
||||
# @return [Boolean] true if the current value is `true`, else false
|
||||
|
||||
# @!macro atomic_boolean_method_false_question
|
||||
#
|
||||
# Is the current value `false`
|
||||
#
|
||||
# @return [Boolean] true if the current value is `false`, else false
|
||||
|
||||
# @!macro atomic_boolean_method_make_true
|
||||
#
|
||||
# Explicitly sets the value to true.
|
||||
#
|
||||
# @return [Boolean] true if value has changed, otherwise false
|
||||
|
||||
# @!macro atomic_boolean_method_make_false
|
||||
#
|
||||
# Explicitly sets the value to false.
|
||||
#
|
||||
# @return [Boolean] true if value has changed, otherwise false
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro atomic_boolean_public_api
|
||||
#
|
||||
# @!method initialize(initial = false)
|
||||
# @!macro atomic_boolean_method_initialize
|
||||
#
|
||||
# @!method value
|
||||
# @!macro atomic_boolean_method_value_get
|
||||
#
|
||||
# @!method value=(value)
|
||||
# @!macro atomic_boolean_method_value_set
|
||||
#
|
||||
# @!method true?
|
||||
# @!macro atomic_boolean_method_true_question
|
||||
#
|
||||
# @!method false?
|
||||
# @!macro atomic_boolean_method_false_question
|
||||
#
|
||||
# @!method make_true
|
||||
# @!macro atomic_boolean_method_make_true
|
||||
#
|
||||
# @!method make_false
|
||||
# @!macro atomic_boolean_method_make_false
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
AtomicBooleanImplementation = case
|
||||
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
||||
CAtomicBoolean
|
||||
when Concurrent.on_jruby?
|
||||
JavaAtomicBoolean
|
||||
else
|
||||
MutexAtomicBoolean
|
||||
end
|
||||
private_constant :AtomicBooleanImplementation
|
||||
|
||||
# @!macro atomic_boolean
|
||||
#
|
||||
# A boolean value that can be updated atomically. Reads and writes to an atomic
|
||||
# boolean and thread-safe and guaranteed to succeed. Reads and writes may block
|
||||
# briefly but no explicit locking is required.
|
||||
#
|
||||
# @!macro thread_safe_variable_comparison
|
||||
#
|
||||
# Performance:
|
||||
#
|
||||
# ```
|
||||
# Testing with ruby 2.1.2
|
||||
# Testing with Concurrent::MutexAtomicBoolean...
|
||||
# 2.790000 0.000000 2.790000 ( 2.791454)
|
||||
# Testing with Concurrent::CAtomicBoolean...
|
||||
# 0.740000 0.000000 0.740000 ( 0.740206)
|
||||
#
|
||||
# Testing with jruby 1.9.3
|
||||
# Testing with Concurrent::MutexAtomicBoolean...
|
||||
# 5.240000 2.520000 7.760000 ( 3.683000)
|
||||
# Testing with Concurrent::JavaAtomicBoolean...
|
||||
# 3.340000 0.010000 3.350000 ( 0.855000)
|
||||
# ```
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html java.util.concurrent.atomic.AtomicBoolean
|
||||
#
|
||||
# @!macro atomic_boolean_public_api
|
||||
class AtomicBoolean < AtomicBooleanImplementation
|
||||
# @return [String] Short string representation.
|
||||
def to_s
|
||||
format '%s value:%s>', super[0..-2], value
|
||||
end
|
||||
|
||||
alias_method :inspect, :to_s
|
||||
end
|
||||
end
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
require 'concurrent/utility/native_extension_loader' # load native parts first
|
||||
|
||||
require 'concurrent/atomic/mutex_atomic_fixnum'
|
||||
|
||||
module Concurrent
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro atomic_fixnum_method_initialize
|
||||
#
|
||||
# Creates a new `AtomicFixnum` with the given initial value.
|
||||
#
|
||||
# @param [Fixnum] initial the initial value
|
||||
# @raise [ArgumentError] if the initial value is not a `Fixnum`
|
||||
|
||||
# @!macro atomic_fixnum_method_value_get
|
||||
#
|
||||
# Retrieves the current `Fixnum` value.
|
||||
#
|
||||
# @return [Fixnum] the current value
|
||||
|
||||
# @!macro atomic_fixnum_method_value_set
|
||||
#
|
||||
# Explicitly sets the value.
|
||||
#
|
||||
# @param [Fixnum] value the new value to be set
|
||||
#
|
||||
# @return [Fixnum] the current value
|
||||
#
|
||||
# @raise [ArgumentError] if the new value is not a `Fixnum`
|
||||
|
||||
# @!macro atomic_fixnum_method_increment
|
||||
#
|
||||
# Increases the current value by the given amount (defaults to 1).
|
||||
#
|
||||
# @param [Fixnum] delta the amount by which to increase the current value
|
||||
#
|
||||
# @return [Fixnum] the current value after incrementation
|
||||
|
||||
# @!macro atomic_fixnum_method_decrement
|
||||
#
|
||||
# Decreases the current value by the given amount (defaults to 1).
|
||||
#
|
||||
# @param [Fixnum] delta the amount by which to decrease the current value
|
||||
#
|
||||
# @return [Fixnum] the current value after decrementation
|
||||
|
||||
# @!macro atomic_fixnum_method_compare_and_set
|
||||
#
|
||||
# Atomically sets the value to the given updated value if the current
|
||||
# value == the expected value.
|
||||
#
|
||||
# @param [Fixnum] expect the expected value
|
||||
# @param [Fixnum] update the new value
|
||||
#
|
||||
# @return [Boolean] true if the value was updated else false
|
||||
|
||||
# @!macro atomic_fixnum_method_update
|
||||
#
|
||||
# Pass the current value to the given block, replacing it
|
||||
# with the block's result. May retry if the value changes
|
||||
# during the block's execution.
|
||||
#
|
||||
# @yield [Object] Calculate a new value for the atomic reference using
|
||||
# given (old) value
|
||||
# @yieldparam [Object] old_value the starting value of the atomic reference
|
||||
#
|
||||
# @return [Object] the new value
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro atomic_fixnum_public_api
|
||||
#
|
||||
# @!method initialize(initial = 0)
|
||||
# @!macro atomic_fixnum_method_initialize
|
||||
#
|
||||
# @!method value
|
||||
# @!macro atomic_fixnum_method_value_get
|
||||
#
|
||||
# @!method value=(value)
|
||||
# @!macro atomic_fixnum_method_value_set
|
||||
#
|
||||
# @!method increment(delta = 1)
|
||||
# @!macro atomic_fixnum_method_increment
|
||||
#
|
||||
# @!method decrement(delta = 1)
|
||||
# @!macro atomic_fixnum_method_decrement
|
||||
#
|
||||
# @!method compare_and_set(expect, update)
|
||||
# @!macro atomic_fixnum_method_compare_and_set
|
||||
#
|
||||
# @!method update
|
||||
# @!macro atomic_fixnum_method_update
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
AtomicFixnumImplementation = case
|
||||
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
||||
CAtomicFixnum
|
||||
when Concurrent.on_jruby?
|
||||
JavaAtomicFixnum
|
||||
else
|
||||
MutexAtomicFixnum
|
||||
end
|
||||
private_constant :AtomicFixnumImplementation
|
||||
|
||||
# @!macro atomic_fixnum
|
||||
#
|
||||
# A numeric value that can be updated atomically. Reads and writes to an atomic
|
||||
# fixnum and thread-safe and guaranteed to succeed. Reads and writes may block
|
||||
# briefly but no explicit locking is required.
|
||||
#
|
||||
# @!macro thread_safe_variable_comparison
|
||||
#
|
||||
# Performance:
|
||||
#
|
||||
# ```
|
||||
# Testing with ruby 2.1.2
|
||||
# Testing with Concurrent::MutexAtomicFixnum...
|
||||
# 3.130000 0.000000 3.130000 ( 3.136505)
|
||||
# Testing with Concurrent::CAtomicFixnum...
|
||||
# 0.790000 0.000000 0.790000 ( 0.785550)
|
||||
#
|
||||
# Testing with jruby 1.9.3
|
||||
# Testing with Concurrent::MutexAtomicFixnum...
|
||||
# 5.460000 2.460000 7.920000 ( 3.715000)
|
||||
# Testing with Concurrent::JavaAtomicFixnum...
|
||||
# 4.520000 0.030000 4.550000 ( 1.187000)
|
||||
# ```
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html java.util.concurrent.atomic.AtomicLong
|
||||
#
|
||||
# @!macro atomic_fixnum_public_api
|
||||
class AtomicFixnum < AtomicFixnumImplementation
|
||||
# @return [String] Short string representation.
|
||||
def to_s
|
||||
format '%s value:%s>', super[0..-2], value
|
||||
end
|
||||
|
||||
alias_method :inspect, :to_s
|
||||
end
|
||||
end
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
require 'concurrent/errors'
|
||||
require 'concurrent/synchronization/object'
|
||||
|
||||
module Concurrent
|
||||
# An atomic reference which maintains an object reference along with a mark bit
|
||||
# that can be updated atomically.
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html
|
||||
# java.util.concurrent.atomic.AtomicMarkableReference
|
||||
class AtomicMarkableReference < ::Concurrent::Synchronization::Object
|
||||
|
||||
attr_atomic(:reference)
|
||||
private :reference, :reference=, :swap_reference, :compare_and_set_reference, :update_reference
|
||||
|
||||
def initialize(value = nil, mark = false)
|
||||
super()
|
||||
self.reference = immutable_array(value, mark)
|
||||
end
|
||||
|
||||
# Atomically sets the value and mark to the given updated value and
|
||||
# mark given both:
|
||||
# - the current value == the expected value &&
|
||||
# - the current mark == the expected mark
|
||||
#
|
||||
# @param [Object] expected_val the expected value
|
||||
# @param [Object] new_val the new value
|
||||
# @param [Boolean] expected_mark the expected mark
|
||||
# @param [Boolean] new_mark the new mark
|
||||
#
|
||||
# @return [Boolean] `true` if successful. A `false` return indicates
|
||||
# that the actual value was not equal to the expected value or the
|
||||
# actual mark was not equal to the expected mark
|
||||
def compare_and_set(expected_val, new_val, expected_mark, new_mark)
|
||||
# Memoize a valid reference to the current AtomicReference for
|
||||
# later comparison.
|
||||
current = reference
|
||||
curr_val, curr_mark = current
|
||||
|
||||
# Ensure that that the expected marks match.
|
||||
return false unless expected_mark == curr_mark
|
||||
|
||||
if expected_val.is_a? Numeric
|
||||
# If the object is a numeric, we need to ensure we are comparing
|
||||
# the numerical values
|
||||
return false unless expected_val == curr_val
|
||||
else
|
||||
# Otherwise, we need to ensure we are comparing the object identity.
|
||||
# Theoretically, this could be incorrect if a user monkey-patched
|
||||
# `Object#equal?`, but they should know that they are playing with
|
||||
# fire at that point.
|
||||
return false unless expected_val.equal? curr_val
|
||||
end
|
||||
|
||||
prospect = immutable_array(new_val, new_mark)
|
||||
|
||||
compare_and_set_reference current, prospect
|
||||
end
|
||||
|
||||
alias_method :compare_and_swap, :compare_and_set
|
||||
|
||||
# Gets the current reference and marked values.
|
||||
#
|
||||
# @return [Array] the current reference and marked values
|
||||
def get
|
||||
reference
|
||||
end
|
||||
|
||||
# Gets the current value of the reference
|
||||
#
|
||||
# @return [Object] the current value of the reference
|
||||
def value
|
||||
reference[0]
|
||||
end
|
||||
|
||||
# Gets the current marked value
|
||||
#
|
||||
# @return [Boolean] the current marked value
|
||||
def mark
|
||||
reference[1]
|
||||
end
|
||||
|
||||
alias_method :marked?, :mark
|
||||
|
||||
# _Unconditionally_ sets to the given value of both the reference and
|
||||
# the mark.
|
||||
#
|
||||
# @param [Object] new_val the new value
|
||||
# @param [Boolean] new_mark the new mark
|
||||
#
|
||||
# @return [Array] both the new value and the new mark
|
||||
def set(new_val, new_mark)
|
||||
self.reference = immutable_array(new_val, new_mark)
|
||||
end
|
||||
|
||||
# Pass the current value and marked state to the given block, replacing it
|
||||
# with the block's results. May retry if the value changes during the
|
||||
# block's execution.
|
||||
#
|
||||
# @yield [Object] Calculate a new value and marked state for the atomic
|
||||
# reference using given (old) value and (old) marked
|
||||
# @yieldparam [Object] old_val the starting value of the atomic reference
|
||||
# @yieldparam [Boolean] old_mark the starting state of marked
|
||||
#
|
||||
# @return [Array] the new value and new mark
|
||||
def update
|
||||
loop do
|
||||
old_val, old_mark = reference
|
||||
new_val, new_mark = yield old_val, old_mark
|
||||
|
||||
if compare_and_set old_val, new_val, old_mark, new_mark
|
||||
return immutable_array(new_val, new_mark)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Pass the current value to the given block, replacing it
|
||||
# with the block's result. Raise an exception if the update
|
||||
# fails.
|
||||
#
|
||||
# @yield [Object] Calculate a new value and marked state for the atomic
|
||||
# reference using given (old) value and (old) marked
|
||||
# @yieldparam [Object] old_val the starting value of the atomic reference
|
||||
# @yieldparam [Boolean] old_mark the starting state of marked
|
||||
#
|
||||
# @return [Array] the new value and marked state
|
||||
#
|
||||
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
||||
def try_update!
|
||||
old_val, old_mark = reference
|
||||
new_val, new_mark = yield old_val, old_mark
|
||||
|
||||
unless compare_and_set old_val, new_val, old_mark, new_mark
|
||||
fail ::Concurrent::ConcurrentUpdateError,
|
||||
'AtomicMarkableReference: Update failed due to race condition.',
|
||||
'Note: If you would like to guarantee an update, please use ' +
|
||||
'the `AtomicMarkableReference#update` method.'
|
||||
end
|
||||
|
||||
immutable_array(new_val, new_mark)
|
||||
end
|
||||
|
||||
# Pass the current value to the given block, replacing it with the
|
||||
# block's result. Simply return nil if update fails.
|
||||
#
|
||||
# @yield [Object] Calculate a new value and marked state for the atomic
|
||||
# reference using given (old) value and (old) marked
|
||||
# @yieldparam [Object] old_val the starting value of the atomic reference
|
||||
# @yieldparam [Boolean] old_mark the starting state of marked
|
||||
#
|
||||
# @return [Array] the new value and marked state, or nil if
|
||||
# the update failed
|
||||
def try_update
|
||||
old_val, old_mark = reference
|
||||
new_val, new_mark = yield old_val, old_mark
|
||||
|
||||
return unless compare_and_set old_val, new_val, old_mark, new_mark
|
||||
|
||||
immutable_array(new_val, new_mark)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def immutable_array(*args)
|
||||
args.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
require 'concurrent/utility/native_extension_loader' # load native parts first
|
||||
|
||||
require 'concurrent/atomic_reference/atomic_direct_update'
|
||||
require 'concurrent/atomic_reference/numeric_cas_wrapper'
|
||||
require 'concurrent/atomic_reference/mutex_atomic'
|
||||
|
||||
# Shim for TruffleRuby::AtomicReference
|
||||
if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference)
|
||||
# @!visibility private
|
||||
module TruffleRuby
|
||||
AtomicReference = Truffle::AtomicReference
|
||||
end
|
||||
end
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro internal_implementation_note
|
||||
AtomicReferenceImplementation = case
|
||||
when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class CAtomicReference
|
||||
include AtomicDirectUpdate
|
||||
include AtomicNumericCompareAndSetWrapper
|
||||
alias_method :compare_and_swap, :compare_and_set
|
||||
end
|
||||
CAtomicReference
|
||||
when Concurrent.on_jruby?
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class JavaAtomicReference
|
||||
include AtomicDirectUpdate
|
||||
end
|
||||
JavaAtomicReference
|
||||
when Concurrent.on_truffleruby?
|
||||
class TruffleRubyAtomicReference < TruffleRuby::AtomicReference
|
||||
include AtomicDirectUpdate
|
||||
alias_method :value, :get
|
||||
alias_method :value=, :set
|
||||
alias_method :compare_and_swap, :compare_and_set
|
||||
alias_method :swap, :get_and_set
|
||||
end
|
||||
TruffleRubyAtomicReference
|
||||
else
|
||||
MutexAtomicReference
|
||||
end
|
||||
private_constant :AtomicReferenceImplementation
|
||||
|
||||
# An object reference that may be updated atomically. All read and write
|
||||
# operations have java volatile semantic.
|
||||
#
|
||||
# @!macro thread_safe_variable_comparison
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
|
||||
# @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
|
||||
#
|
||||
# @!method initialize(value = nil)
|
||||
# @!macro atomic_reference_method_initialize
|
||||
# @param [Object] value The initial value.
|
||||
#
|
||||
# @!method get
|
||||
# @!macro atomic_reference_method_get
|
||||
# Gets the current value.
|
||||
# @return [Object] the current value
|
||||
#
|
||||
# @!method set(new_value)
|
||||
# @!macro atomic_reference_method_set
|
||||
# Sets to the given value.
|
||||
# @param [Object] new_value the new value
|
||||
# @return [Object] the new value
|
||||
#
|
||||
# @!method get_and_set(new_value)
|
||||
# @!macro atomic_reference_method_get_and_set
|
||||
# Atomically sets to the given value and returns the old value.
|
||||
# @param [Object] new_value the new value
|
||||
# @return [Object] the old value
|
||||
#
|
||||
# @!method compare_and_set(old_value, new_value)
|
||||
# @!macro atomic_reference_method_compare_and_set
|
||||
#
|
||||
# Atomically sets the value to the given updated value if
|
||||
# the current value == the expected value.
|
||||
#
|
||||
# @param [Object] old_value the expected value
|
||||
# @param [Object] new_value the new value
|
||||
#
|
||||
# @return [Boolean] `true` if successful. A `false` return indicates
|
||||
# that the actual value was not equal to the expected value.
|
||||
#
|
||||
# @!method update
|
||||
# Pass the current value to the given block, replacing it
|
||||
# with the block's result. May retry if the value changes
|
||||
# during the block's execution.
|
||||
#
|
||||
# @yield [Object] Calculate a new value for the atomic reference using
|
||||
# given (old) value
|
||||
# @yieldparam [Object] old_value the starting value of the atomic reference
|
||||
# @return [Object] the new value
|
||||
#
|
||||
# @!method try_update
|
||||
# Pass the current value to the given block, replacing it
|
||||
# with the block's result. Return nil if the update fails.
|
||||
#
|
||||
# @yield [Object] Calculate a new value for the atomic reference using
|
||||
# given (old) value
|
||||
# @yieldparam [Object] old_value the starting value of the atomic reference
|
||||
# @note This method was altered to avoid raising an exception by default.
|
||||
# Instead, this method now returns `nil` in case of failure. For more info,
|
||||
# please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
||||
# @return [Object] the new value, or nil if update failed
|
||||
#
|
||||
# @!method try_update!
|
||||
# Pass the current value to the given block, replacing it
|
||||
# with the block's result. Raise an exception if the update
|
||||
# fails.
|
||||
#
|
||||
# @yield [Object] Calculate a new value for the atomic reference using
|
||||
# given (old) value
|
||||
# @yieldparam [Object] old_value the starting value of the atomic reference
|
||||
# @note This behavior mimics the behavior of the original
|
||||
# `AtomicReference#try_update` API. The reason this was changed was to
|
||||
# avoid raising exceptions (which are inherently slow) by default. For more
|
||||
# info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
|
||||
# @return [Object] the new value
|
||||
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
|
||||
class AtomicReference < AtomicReferenceImplementation
|
||||
|
||||
# @return [String] Short string representation.
|
||||
def to_s
|
||||
format '%s value:%s>', super[0..-2], get
|
||||
end
|
||||
|
||||
alias_method :inspect, :to_s
|
||||
end
|
||||
end
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
require 'concurrent/utility/engine'
|
||||
require 'concurrent/atomic/mutex_count_down_latch'
|
||||
require 'concurrent/atomic/java_count_down_latch'
|
||||
|
||||
module Concurrent
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro count_down_latch_method_initialize
|
||||
#
|
||||
# Create a new `CountDownLatch` with the initial `count`.
|
||||
#
|
||||
# @param [new] count the initial count
|
||||
#
|
||||
# @raise [ArgumentError] if `count` is not an integer or is less than zero
|
||||
|
||||
# @!macro count_down_latch_method_wait
|
||||
#
|
||||
# Block on the latch until the counter reaches zero or until `timeout` is reached.
|
||||
#
|
||||
# @param [Fixnum] timeout the number of seconds to wait for the counter or `nil`
|
||||
# to block indefinitely
|
||||
# @return [Boolean] `true` if the `count` reaches zero else false on `timeout`
|
||||
|
||||
# @!macro count_down_latch_method_count_down
|
||||
#
|
||||
# Signal the latch to decrement the counter. Will signal all blocked threads when
|
||||
# the `count` reaches zero.
|
||||
|
||||
# @!macro count_down_latch_method_count
|
||||
#
|
||||
# The current value of the counter.
|
||||
#
|
||||
# @return [Fixnum] the current value of the counter
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro count_down_latch_public_api
|
||||
#
|
||||
# @!method initialize(count = 1)
|
||||
# @!macro count_down_latch_method_initialize
|
||||
#
|
||||
# @!method wait(timeout = nil)
|
||||
# @!macro count_down_latch_method_wait
|
||||
#
|
||||
# @!method count_down
|
||||
# @!macro count_down_latch_method_count_down
|
||||
#
|
||||
# @!method count
|
||||
# @!macro count_down_latch_method_count
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
CountDownLatchImplementation = case
|
||||
when Concurrent.on_jruby?
|
||||
JavaCountDownLatch
|
||||
else
|
||||
MutexCountDownLatch
|
||||
end
|
||||
private_constant :CountDownLatchImplementation
|
||||
|
||||
# @!macro count_down_latch
|
||||
#
|
||||
# A synchronization object that allows one thread to wait on multiple other threads.
|
||||
# The thread that will wait creates a `CountDownLatch` and sets the initial value
|
||||
# (normally equal to the number of other threads). The initiating thread passes the
|
||||
# latch to the other threads then waits for the other threads by calling the `#wait`
|
||||
# method. Each of the other threads calls `#count_down` when done with its work.
|
||||
# When the latch counter reaches zero the waiting thread is unblocked and continues
|
||||
# with its work. A `CountDownLatch` can be used only once. Its value cannot be reset.
|
||||
#
|
||||
# @!macro count_down_latch_public_api
|
||||
# @example Waiter and Decrementer
|
||||
# latch = Concurrent::CountDownLatch.new(3)
|
||||
#
|
||||
# waiter = Thread.new do
|
||||
# latch.wait()
|
||||
# puts ("Waiter released")
|
||||
# end
|
||||
#
|
||||
# decrementer = Thread.new do
|
||||
# sleep(1)
|
||||
# latch.count_down
|
||||
# puts latch.count
|
||||
#
|
||||
# sleep(1)
|
||||
# latch.count_down
|
||||
# puts latch.count
|
||||
#
|
||||
# sleep(1)
|
||||
# latch.count_down
|
||||
# puts latch.count
|
||||
# end
|
||||
#
|
||||
# [waiter, decrementer].each(&:join)
|
||||
class CountDownLatch < CountDownLatchImplementation
|
||||
end
|
||||
end
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
require 'concurrent/utility/native_integer'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# A synchronization aid that allows a set of threads to all wait for each
|
||||
# other to reach a common barrier point.
|
||||
# @example
|
||||
# barrier = Concurrent::CyclicBarrier.new(3)
|
||||
# jobs = Array.new(3) { |i| -> { sleep i; p done: i } }
|
||||
# process = -> (i) do
|
||||
# # waiting to start at the same time
|
||||
# barrier.wait
|
||||
# # execute job
|
||||
# jobs[i].call
|
||||
# # wait for others to finish
|
||||
# barrier.wait
|
||||
# end
|
||||
# threads = 2.times.map do |i|
|
||||
# Thread.new(i, &process)
|
||||
# end
|
||||
#
|
||||
# # use main as well
|
||||
# process.call 2
|
||||
#
|
||||
# # here we can be sure that all jobs are processed
|
||||
class CyclicBarrier < Synchronization::LockableObject
|
||||
|
||||
# @!visibility private
|
||||
Generation = Struct.new(:status)
|
||||
private_constant :Generation
|
||||
|
||||
# Create a new `CyclicBarrier` that waits for `parties` threads
|
||||
#
|
||||
# @param [Fixnum] parties the number of parties
|
||||
# @yield an optional block that will be executed that will be executed after
|
||||
# the last thread arrives and before the others are released
|
||||
#
|
||||
# @raise [ArgumentError] if `parties` is not an integer or is less than zero
|
||||
def initialize(parties, &block)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds parties
|
||||
Utility::NativeInteger.ensure_positive_and_no_zero parties
|
||||
|
||||
super(&nil)
|
||||
synchronize { ns_initialize parties, &block }
|
||||
end
|
||||
|
||||
# @return [Fixnum] the number of threads needed to pass the barrier
|
||||
def parties
|
||||
synchronize { @parties }
|
||||
end
|
||||
|
||||
# @return [Fixnum] the number of threads currently waiting on the barrier
|
||||
def number_waiting
|
||||
synchronize { @number_waiting }
|
||||
end
|
||||
|
||||
# Blocks on the barrier until the number of waiting threads is equal to
|
||||
# `parties` or until `timeout` is reached or `reset` is called
|
||||
# If a block has been passed to the constructor, it will be executed once by
|
||||
# the last arrived thread before releasing the others
|
||||
# @param [Fixnum] timeout the number of seconds to wait for the counter or
|
||||
# `nil` to block indefinitely
|
||||
# @return [Boolean] `true` if the `count` reaches zero else false on
|
||||
# `timeout` or on `reset` or if the barrier is broken
|
||||
def wait(timeout = nil)
|
||||
synchronize do
|
||||
|
||||
return false unless @generation.status == :waiting
|
||||
|
||||
@number_waiting += 1
|
||||
|
||||
if @number_waiting == @parties
|
||||
@action.call if @action
|
||||
ns_generation_done @generation, :fulfilled
|
||||
true
|
||||
else
|
||||
generation = @generation
|
||||
if ns_wait_until(timeout) { generation.status != :waiting }
|
||||
generation.status == :fulfilled
|
||||
else
|
||||
ns_generation_done generation, :broken, false
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# resets the barrier to its initial state
|
||||
# If there is at least one waiting thread, it will be woken up, the `wait`
|
||||
# method will return false and the barrier will be broken
|
||||
# If the barrier is broken, this method restores it to the original state
|
||||
#
|
||||
# @return [nil]
|
||||
def reset
|
||||
synchronize { ns_generation_done @generation, :reset }
|
||||
end
|
||||
|
||||
# A barrier can be broken when:
|
||||
# - a thread called the `reset` method while at least one other thread was waiting
|
||||
# - at least one thread timed out on `wait` method
|
||||
#
|
||||
# A broken barrier can be restored using `reset` it's safer to create a new one
|
||||
# @return [Boolean] true if the barrier is broken otherwise false
|
||||
def broken?
|
||||
synchronize { @generation.status != :waiting }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ns_generation_done(generation, status, continue = true)
|
||||
generation.status = status
|
||||
ns_next_generation if continue
|
||||
ns_broadcast
|
||||
end
|
||||
|
||||
def ns_next_generation
|
||||
@generation = Generation.new(:waiting)
|
||||
@number_waiting = 0
|
||||
end
|
||||
|
||||
def ns_initialize(parties, &block)
|
||||
@parties = parties
|
||||
@action = block
|
||||
ns_next_generation
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,109 @@
|
||||
require 'thread'
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# Old school kernel-style event reminiscent of Win32 programming in C++.
|
||||
#
|
||||
# When an `Event` is created it is in the `unset` state. Threads can choose to
|
||||
# `#wait` on the event, blocking until released by another thread. When one
|
||||
# thread wants to alert all blocking threads it calls the `#set` method which
|
||||
# will then wake up all listeners. Once an `Event` has been set it remains set.
|
||||
# New threads calling `#wait` will return immediately. An `Event` may be
|
||||
# `#reset` at any time once it has been set.
|
||||
#
|
||||
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx
|
||||
# @example
|
||||
# event = Concurrent::Event.new
|
||||
#
|
||||
# t1 = Thread.new do
|
||||
# puts "t1 is waiting"
|
||||
# event.wait(1)
|
||||
# puts "event occurred"
|
||||
# end
|
||||
#
|
||||
# t2 = Thread.new do
|
||||
# puts "t2 calling set"
|
||||
# event.set
|
||||
# end
|
||||
#
|
||||
# [t1, t2].each(&:join)
|
||||
#
|
||||
# # prints:
|
||||
# # t1 is waiting
|
||||
# # t2 calling set
|
||||
# # event occurred
|
||||
class Event < Synchronization::LockableObject
|
||||
|
||||
# Creates a new `Event` in the unset state. Threads calling `#wait` on the
|
||||
# `Event` will block.
|
||||
def initialize
|
||||
super
|
||||
synchronize { ns_initialize }
|
||||
end
|
||||
|
||||
# Is the object in the set state?
|
||||
#
|
||||
# @return [Boolean] indicating whether or not the `Event` has been set
|
||||
def set?
|
||||
synchronize { @set }
|
||||
end
|
||||
|
||||
# Trigger the event, setting the state to `set` and releasing all threads
|
||||
# waiting on the event. Has no effect if the `Event` has already been set.
|
||||
#
|
||||
# @return [Boolean] should always return `true`
|
||||
def set
|
||||
synchronize { ns_set }
|
||||
end
|
||||
|
||||
def try?
|
||||
synchronize { @set ? false : ns_set }
|
||||
end
|
||||
|
||||
# Reset a previously set event back to the `unset` state.
|
||||
# Has no effect if the `Event` has not yet been set.
|
||||
#
|
||||
# @return [Boolean] should always return `true`
|
||||
def reset
|
||||
synchronize do
|
||||
if @set
|
||||
@set = false
|
||||
@iteration +=1
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Wait a given number of seconds for the `Event` to be set by another
|
||||
# thread. Will wait forever when no `timeout` value is given. Returns
|
||||
# immediately if the `Event` has already been set.
|
||||
#
|
||||
# @return [Boolean] true if the `Event` was set before timeout else false
|
||||
def wait(timeout = nil)
|
||||
synchronize do
|
||||
unless @set
|
||||
iteration = @iteration
|
||||
ns_wait_until(timeout) { iteration < @iteration || @set }
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ns_set
|
||||
unless @set
|
||||
@set = true
|
||||
ns_broadcast
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def ns_initialize
|
||||
@set = false
|
||||
@iteration = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
require 'concurrent/constants'
|
||||
require_relative 'locals'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# A `FiberLocalVar` is a variable where the value is different for each fiber.
|
||||
# Each variable may have a default value, but when you modify the variable only
|
||||
# the current fiber will ever see that change.
|
||||
#
|
||||
# This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`),
|
||||
# but with these major advantages:
|
||||
# * `FiberLocalVar` has its own identity, it doesn't need a Symbol.
|
||||
# * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber),
|
||||
# so it's only OK to create a small amount of them.
|
||||
# `FiberLocalVar` has no such issue and it is fine to create many of them.
|
||||
# * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly).
|
||||
# `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd.
|
||||
#
|
||||
# @example
|
||||
# v = FiberLocalVar.new(14)
|
||||
# v.value #=> 14
|
||||
# v.value = 2
|
||||
# v.value #=> 2
|
||||
#
|
||||
# @example
|
||||
# v = FiberLocalVar.new(14)
|
||||
#
|
||||
# Fiber.new do
|
||||
# v.value #=> 14
|
||||
# v.value = 1
|
||||
# v.value #=> 1
|
||||
# end.resume
|
||||
#
|
||||
# Fiber.new do
|
||||
# v.value #=> 14
|
||||
# v.value = 2
|
||||
# v.value #=> 2
|
||||
# end.resume
|
||||
#
|
||||
# v.value #=> 14
|
||||
class FiberLocalVar
|
||||
LOCALS = FiberLocals.new
|
||||
|
||||
# Creates a fiber local variable.
|
||||
#
|
||||
# @param [Object] default the default value when otherwise unset
|
||||
# @param [Proc] default_block Optional block that gets called to obtain the
|
||||
# default value for each fiber
|
||||
def initialize(default = nil, &default_block)
|
||||
if default && block_given?
|
||||
raise ArgumentError, "Cannot use both value and block as default value"
|
||||
end
|
||||
|
||||
if block_given?
|
||||
@default_block = default_block
|
||||
@default = nil
|
||||
else
|
||||
@default_block = nil
|
||||
@default = default
|
||||
end
|
||||
|
||||
@index = LOCALS.next_index(self)
|
||||
end
|
||||
|
||||
# Returns the value in the current fiber's copy of this fiber-local variable.
|
||||
#
|
||||
# @return [Object] the current value
|
||||
def value
|
||||
LOCALS.fetch(@index) { default }
|
||||
end
|
||||
|
||||
# Sets the current fiber's copy of this fiber-local variable to the specified value.
|
||||
#
|
||||
# @param [Object] value the value to set
|
||||
# @return [Object] the new value
|
||||
def value=(value)
|
||||
LOCALS.set(@index, value)
|
||||
end
|
||||
|
||||
# Bind the given value to fiber local storage during
|
||||
# execution of the given block.
|
||||
#
|
||||
# @param [Object] value the value to bind
|
||||
# @yield the operation to be performed with the bound variable
|
||||
# @return [Object] the value
|
||||
def bind(value)
|
||||
if block_given?
|
||||
old_value = self.value
|
||||
self.value = value
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
self.value = old_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def default
|
||||
if @default_block
|
||||
self.value = @default_block.call
|
||||
else
|
||||
@default
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
if Concurrent.on_jruby?
|
||||
require 'concurrent/utility/native_extension_loader'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro count_down_latch
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class JavaCountDownLatch
|
||||
|
||||
# @!macro count_down_latch_method_initialize
|
||||
def initialize(count = 1)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds(count)
|
||||
Utility::NativeInteger.ensure_positive(count)
|
||||
@latch = java.util.concurrent.CountDownLatch.new(count)
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_wait
|
||||
def wait(timeout = nil)
|
||||
result = nil
|
||||
if timeout.nil?
|
||||
Synchronization::JRuby.sleep_interruptibly { @latch.await }
|
||||
result = true
|
||||
else
|
||||
Synchronization::JRuby.sleep_interruptibly do
|
||||
result = @latch.await(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_count_down
|
||||
def count_down
|
||||
@latch.countDown
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_count
|
||||
def count
|
||||
@latch.getCount
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
require 'fiber'
|
||||
require 'concurrent/utility/engine'
|
||||
require 'concurrent/constants'
|
||||
|
||||
module Concurrent
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
#
|
||||
# An abstract implementation of local storage, with sub-classes for
|
||||
# per-thread and per-fiber locals.
|
||||
#
|
||||
# Each execution context (EC, thread or fiber) has a lazily initialized array
|
||||
# of local variable values. Each time a new local variable is created, we
|
||||
# allocate an "index" for it.
|
||||
#
|
||||
# For example, if the allocated index is 1, that means slot #1 in EVERY EC's
|
||||
# locals array will be used for the value of that variable.
|
||||
#
|
||||
# The good thing about using a per-EC structure to hold values, rather than
|
||||
# a global, is that no synchronization is needed when reading and writing
|
||||
# those values (since the structure is only ever accessed by a single
|
||||
# thread).
|
||||
#
|
||||
# Of course, when a local variable is GC'd, 1) we need to recover its index
|
||||
# for use by other new local variables (otherwise the locals arrays could
|
||||
# get bigger and bigger with time), and 2) we need to null out all the
|
||||
# references held in the now-unused slots (both to avoid blocking GC of those
|
||||
# objects, and also to prevent "stale" values from being passed on to a new
|
||||
# local when the index is reused).
|
||||
#
|
||||
# Because we need to null out freed slots, we need to keep references to
|
||||
# ALL the locals arrays, so we can null out the appropriate slots in all of
|
||||
# them. This is why we need to use a finalizer to clean up the locals array
|
||||
# when the EC goes out of scope.
|
||||
class AbstractLocals
|
||||
def initialize
|
||||
@free = []
|
||||
@lock = Mutex.new
|
||||
@all_arrays = {}
|
||||
@next = 0
|
||||
end
|
||||
|
||||
def synchronize
|
||||
@lock.synchronize { yield }
|
||||
end
|
||||
|
||||
if Concurrent.on_cruby?
|
||||
def weak_synchronize
|
||||
yield
|
||||
end
|
||||
else
|
||||
alias_method :weak_synchronize, :synchronize
|
||||
end
|
||||
|
||||
def next_index(local)
|
||||
index = synchronize do
|
||||
if @free.empty?
|
||||
@next += 1
|
||||
else
|
||||
@free.pop
|
||||
end
|
||||
end
|
||||
|
||||
# When the local goes out of scope, we should free the associated index
|
||||
# and all values stored into it.
|
||||
ObjectSpace.define_finalizer(local, local_finalizer(index))
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def free_index(index)
|
||||
weak_synchronize do
|
||||
# The cost of GC'ing a TLV is linear in the number of ECs using local
|
||||
# variables. But that is natural! More ECs means more storage is used
|
||||
# per local variable. So naturally more CPU time is required to free
|
||||
# more storage.
|
||||
#
|
||||
# DO NOT use each_value which might conflict with new pair assignment
|
||||
# into the hash in #set method.
|
||||
@all_arrays.values.each do |locals|
|
||||
locals[index] = nil
|
||||
end
|
||||
|
||||
# free index has to be published after the arrays are cleared:
|
||||
@free << index
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(index)
|
||||
locals = self.locals
|
||||
value = locals ? locals[index] : nil
|
||||
|
||||
if nil == value
|
||||
yield
|
||||
elsif NULL.equal?(value)
|
||||
nil
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def set(index, value)
|
||||
locals = self.locals!
|
||||
locals[index] = (nil == value ? NULL : value)
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# When the local goes out of scope, clean up that slot across all locals currently assigned.
|
||||
def local_finalizer(index)
|
||||
proc do
|
||||
free_index(index)
|
||||
end
|
||||
end
|
||||
|
||||
# When a thread/fiber goes out of scope, remove the array from @all_arrays.
|
||||
def thread_fiber_finalizer(array_object_id)
|
||||
proc do
|
||||
weak_synchronize do
|
||||
@all_arrays.delete(array_object_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the locals for the current scope, or nil if none exist.
|
||||
def locals
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Returns the locals for the current scope, creating them if necessary.
|
||||
def locals!
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
# An array-backed storage of indexed variables per thread.
|
||||
class ThreadLocals < AbstractLocals
|
||||
def locals
|
||||
Thread.current.thread_variable_get(:concurrent_thread_locals)
|
||||
end
|
||||
|
||||
def locals!
|
||||
thread = Thread.current
|
||||
locals = thread.thread_variable_get(:concurrent_thread_locals)
|
||||
|
||||
unless locals
|
||||
locals = thread.thread_variable_set(:concurrent_thread_locals, [])
|
||||
weak_synchronize do
|
||||
@all_arrays[locals.object_id] = locals
|
||||
end
|
||||
# When the thread goes out of scope, we should delete the associated locals:
|
||||
ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
|
||||
end
|
||||
|
||||
locals
|
||||
end
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
# An array-backed storage of indexed variables per fiber.
|
||||
class FiberLocals < AbstractLocals
|
||||
def locals
|
||||
Thread.current[:concurrent_fiber_locals]
|
||||
end
|
||||
|
||||
def locals!
|
||||
thread = Thread.current
|
||||
locals = thread[:concurrent_fiber_locals]
|
||||
|
||||
unless locals
|
||||
locals = thread[:concurrent_fiber_locals] = []
|
||||
weak_synchronize do
|
||||
@all_arrays[locals.object_id] = locals
|
||||
end
|
||||
# When the fiber goes out of scope, we should delete the associated locals:
|
||||
ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
|
||||
end
|
||||
|
||||
locals
|
||||
end
|
||||
end
|
||||
|
||||
private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
|
||||
end
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
require 'concurrent/utility/engine'
|
||||
require_relative 'fiber_local_var'
|
||||
require_relative 'thread_local_var'
|
||||
|
||||
module Concurrent
|
||||
# @!visibility private
|
||||
def self.mutex_owned_per_thread?
|
||||
return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
|
||||
|
||||
mutex = Mutex.new
|
||||
# Lock the mutex:
|
||||
mutex.synchronize do
|
||||
# Check if the mutex is still owned in a child fiber:
|
||||
Fiber.new { mutex.owned? }.resume
|
||||
end
|
||||
end
|
||||
|
||||
if mutex_owned_per_thread?
|
||||
LockLocalVar = ThreadLocalVar
|
||||
else
|
||||
LockLocalVar = FiberLocalVar
|
||||
end
|
||||
|
||||
# Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor)
|
||||
# are held, respectively, per Fiber or per Thread.
|
||||
class LockLocalVar
|
||||
end
|
||||
end
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
require 'concurrent/synchronization/safe_initialization'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro atomic_boolean
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class MutexAtomicBoolean
|
||||
extend Concurrent::Synchronization::SafeInitialization
|
||||
|
||||
# @!macro atomic_boolean_method_initialize
|
||||
def initialize(initial = false)
|
||||
super()
|
||||
@Lock = ::Mutex.new
|
||||
@value = !!initial
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_value_get
|
||||
def value
|
||||
synchronize { @value }
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_value_set
|
||||
def value=(value)
|
||||
synchronize { @value = !!value }
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_true_question
|
||||
def true?
|
||||
synchronize { @value }
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_false_question
|
||||
def false?
|
||||
synchronize { !@value }
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_make_true
|
||||
def make_true
|
||||
synchronize { ns_make_value(true) }
|
||||
end
|
||||
|
||||
# @!macro atomic_boolean_method_make_false
|
||||
def make_false
|
||||
synchronize { ns_make_value(false) }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def synchronize
|
||||
if @Lock.owned?
|
||||
yield
|
||||
else
|
||||
@Lock.synchronize { yield }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @!visibility private
|
||||
def ns_make_value(value)
|
||||
old = @value
|
||||
@value = value
|
||||
old != @value
|
||||
end
|
||||
end
|
||||
end
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
require 'concurrent/synchronization/safe_initialization'
|
||||
require 'concurrent/utility/native_integer'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro atomic_fixnum
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class MutexAtomicFixnum
|
||||
extend Concurrent::Synchronization::SafeInitialization
|
||||
|
||||
# @!macro atomic_fixnum_method_initialize
|
||||
def initialize(initial = 0)
|
||||
super()
|
||||
@Lock = ::Mutex.new
|
||||
ns_set(initial)
|
||||
end
|
||||
|
||||
# @!macro atomic_fixnum_method_value_get
|
||||
def value
|
||||
synchronize { @value }
|
||||
end
|
||||
|
||||
# @!macro atomic_fixnum_method_value_set
|
||||
def value=(value)
|
||||
synchronize { ns_set(value) }
|
||||
end
|
||||
|
||||
# @!macro atomic_fixnum_method_increment
|
||||
def increment(delta = 1)
|
||||
synchronize { ns_set(@value + delta.to_i) }
|
||||
end
|
||||
|
||||
alias_method :up, :increment
|
||||
|
||||
# @!macro atomic_fixnum_method_decrement
|
||||
def decrement(delta = 1)
|
||||
synchronize { ns_set(@value - delta.to_i) }
|
||||
end
|
||||
|
||||
alias_method :down, :decrement
|
||||
|
||||
# @!macro atomic_fixnum_method_compare_and_set
|
||||
def compare_and_set(expect, update)
|
||||
synchronize do
|
||||
if @value == expect.to_i
|
||||
@value = update.to_i
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro atomic_fixnum_method_update
|
||||
def update
|
||||
synchronize do
|
||||
@value = yield @value
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def synchronize
|
||||
if @Lock.owned?
|
||||
yield
|
||||
else
|
||||
@Lock.synchronize { yield }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @!visibility private
|
||||
def ns_set(value)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds value
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
end
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
require 'concurrent/utility/native_integer'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro count_down_latch
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class MutexCountDownLatch < Synchronization::LockableObject
|
||||
|
||||
# @!macro count_down_latch_method_initialize
|
||||
def initialize(count = 1)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds count
|
||||
Utility::NativeInteger.ensure_positive count
|
||||
|
||||
super()
|
||||
synchronize { ns_initialize count }
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_wait
|
||||
def wait(timeout = nil)
|
||||
synchronize { ns_wait_until(timeout) { @count == 0 } }
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_count_down
|
||||
def count_down
|
||||
synchronize do
|
||||
@count -= 1 if @count > 0
|
||||
ns_broadcast if @count == 0
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro count_down_latch_method_count
|
||||
def count
|
||||
synchronize { @count }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ns_initialize(count)
|
||||
@count = count
|
||||
end
|
||||
end
|
||||
end
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
require 'concurrent/utility/native_integer'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro semaphore
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class MutexSemaphore < Synchronization::LockableObject
|
||||
|
||||
# @!macro semaphore_method_initialize
|
||||
def initialize(count)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds count
|
||||
|
||||
super()
|
||||
synchronize { ns_initialize count }
|
||||
end
|
||||
|
||||
# @!macro semaphore_method_acquire
|
||||
def acquire(permits = 1)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds permits
|
||||
Utility::NativeInteger.ensure_positive permits
|
||||
|
||||
synchronize do
|
||||
try_acquire_timed(permits, nil)
|
||||
end
|
||||
|
||||
return unless block_given?
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release(permits)
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro semaphore_method_available_permits
|
||||
def available_permits
|
||||
synchronize { @free }
|
||||
end
|
||||
|
||||
# @!macro semaphore_method_drain_permits
|
||||
#
|
||||
# Acquires and returns all permits that are immediately available.
|
||||
#
|
||||
# @return [Integer]
|
||||
def drain_permits
|
||||
synchronize do
|
||||
@free.tap { |_| @free = 0 }
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro semaphore_method_try_acquire
|
||||
def try_acquire(permits = 1, timeout = nil)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds permits
|
||||
Utility::NativeInteger.ensure_positive permits
|
||||
|
||||
acquired = synchronize do
|
||||
if timeout.nil?
|
||||
try_acquire_now(permits)
|
||||
else
|
||||
try_acquire_timed(permits, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
return acquired unless block_given?
|
||||
return unless acquired
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release(permits)
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro semaphore_method_release
|
||||
def release(permits = 1)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds permits
|
||||
Utility::NativeInteger.ensure_positive permits
|
||||
|
||||
synchronize do
|
||||
@free += permits
|
||||
permits.times { ns_signal }
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Shrinks the number of available permits by the indicated reduction.
|
||||
#
|
||||
# @param [Fixnum] reduction Number of permits to remove.
|
||||
#
|
||||
# @raise [ArgumentError] if `reduction` is not an integer or is negative
|
||||
#
|
||||
# @raise [ArgumentError] if `@free` - `@reduction` is less than zero
|
||||
#
|
||||
# @return [nil]
|
||||
#
|
||||
# @!visibility private
|
||||
def reduce_permits(reduction)
|
||||
Utility::NativeInteger.ensure_integer_and_bounds reduction
|
||||
Utility::NativeInteger.ensure_positive reduction
|
||||
|
||||
synchronize { @free -= reduction }
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def ns_initialize(count)
|
||||
@free = count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @!visibility private
|
||||
def try_acquire_now(permits)
|
||||
if @free >= permits
|
||||
@free -= permits
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def try_acquire_timed(permits, timeout)
|
||||
ns_wait_until(timeout) { try_acquire_now(permits) }
|
||||
end
|
||||
end
|
||||
end
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
require 'thread'
|
||||
require 'concurrent/atomic/atomic_fixnum'
|
||||
require 'concurrent/errors'
|
||||
require 'concurrent/synchronization/object'
|
||||
require 'concurrent/synchronization/lock'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# Ruby read-write lock implementation
|
||||
#
|
||||
# Allows any number of concurrent readers, but only one concurrent writer
|
||||
# (And if the "write" lock is taken, any readers who come along will have to wait)
|
||||
#
|
||||
# If readers are already active when a writer comes along, the writer will wait for
|
||||
# all the readers to finish before going ahead.
|
||||
# Any additional readers that come when the writer is already waiting, will also
|
||||
# wait (so writers are not starved).
|
||||
#
|
||||
# This implementation is based on `java.util.concurrent.ReentrantReadWriteLock`.
|
||||
#
|
||||
# @example
|
||||
# lock = Concurrent::ReadWriteLock.new
|
||||
# lock.with_read_lock { data.retrieve }
|
||||
# lock.with_write_lock { data.modify! }
|
||||
#
|
||||
# @note Do **not** try to acquire the write lock while already holding a read lock
|
||||
# **or** try to acquire the write lock while you already have it.
|
||||
# This will lead to deadlock
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
|
||||
class ReadWriteLock < Synchronization::Object
|
||||
|
||||
# @!visibility private
|
||||
WAITING_WRITER = 1 << 15
|
||||
|
||||
# @!visibility private
|
||||
RUNNING_WRITER = 1 << 29
|
||||
|
||||
# @!visibility private
|
||||
MAX_READERS = WAITING_WRITER - 1
|
||||
|
||||
# @!visibility private
|
||||
MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
|
||||
|
||||
safe_initialization!
|
||||
|
||||
# Implementation notes:
|
||||
# A goal is to make the uncontended path for both readers/writers lock-free
|
||||
# Only if there is reader-writer or writer-writer contention, should locks be used
|
||||
# Internal state is represented by a single integer ("counter"), and updated
|
||||
# using atomic compare-and-swap operations
|
||||
# When the counter is 0, the lock is free
|
||||
# Each reader increments the counter by 1 when acquiring a read lock
|
||||
# (and decrements by 1 when releasing the read lock)
|
||||
# The counter is increased by (1 << 15) for each writer waiting to acquire the
|
||||
# write lock, and by (1 << 29) if the write lock is taken
|
||||
|
||||
# Create a new `ReadWriteLock` in the unlocked state.
|
||||
def initialize
|
||||
super()
|
||||
@Counter = AtomicFixnum.new(0) # single integer which represents lock state
|
||||
@ReadLock = Synchronization::Lock.new
|
||||
@WriteLock = Synchronization::Lock.new
|
||||
end
|
||||
|
||||
# Execute a block operation within a read lock.
|
||||
#
|
||||
# @yield the task to be performed within the lock.
|
||||
#
|
||||
# @return [Object] the result of the block operation.
|
||||
#
|
||||
# @raise [ArgumentError] when no block is given.
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def with_read_lock
|
||||
raise ArgumentError.new('no block given') unless block_given?
|
||||
acquire_read_lock
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release_read_lock
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a block operation within a write lock.
|
||||
#
|
||||
# @yield the task to be performed within the lock.
|
||||
#
|
||||
# @return [Object] the result of the block operation.
|
||||
#
|
||||
# @raise [ArgumentError] when no block is given.
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def with_write_lock
|
||||
raise ArgumentError.new('no block given') unless block_given?
|
||||
acquire_write_lock
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release_write_lock
|
||||
end
|
||||
end
|
||||
|
||||
# Acquire a read lock. If a write lock has been acquired will block until
|
||||
# it is released. Will not block if other read locks have been acquired.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
#
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def acquire_read_lock
|
||||
while true
|
||||
c = @Counter.value
|
||||
raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
|
||||
|
||||
# If a writer is waiting when we first queue up, we need to wait
|
||||
if waiting_writer?(c)
|
||||
@ReadLock.wait_until { !waiting_writer? }
|
||||
|
||||
# after a reader has waited once, they are allowed to "barge" ahead of waiting writers
|
||||
# but if a writer is *running*, the reader still needs to wait (naturally)
|
||||
while true
|
||||
c = @Counter.value
|
||||
if running_writer?(c)
|
||||
@ReadLock.wait_until { !running_writer? }
|
||||
else
|
||||
return if @Counter.compare_and_set(c, c+1)
|
||||
end
|
||||
end
|
||||
else
|
||||
break if @Counter.compare_and_set(c, c+1)
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Release a previously acquired read lock.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully released
|
||||
def release_read_lock
|
||||
while true
|
||||
c = @Counter.value
|
||||
if @Counter.compare_and_set(c, c-1)
|
||||
# If one or more writers were waiting, and we were the last reader, wake a writer up
|
||||
if waiting_writer?(c) && running_readers(c) == 1
|
||||
@WriteLock.signal
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Acquire a write lock. Will block and wait for all active readers and writers.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
#
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
|
||||
# is exceeded.
|
||||
def acquire_write_lock
|
||||
while true
|
||||
c = @Counter.value
|
||||
raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
|
||||
|
||||
if c == 0 # no readers OR writers running
|
||||
# if we successfully swap the RUNNING_WRITER bit on, then we can go ahead
|
||||
break if @Counter.compare_and_set(0, RUNNING_WRITER)
|
||||
elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
|
||||
while true
|
||||
# Now we have successfully incremented, so no more readers will be able to increment
|
||||
# (they will wait instead)
|
||||
# However, readers OR writers could decrement right here, OR another writer could increment
|
||||
@WriteLock.wait_until do
|
||||
# So we have to do another check inside the synchronized section
|
||||
# If a writer OR reader is running, then go to sleep
|
||||
c = @Counter.value
|
||||
!running_writer?(c) && !running_readers?(c)
|
||||
end
|
||||
|
||||
# We just came out of a wait
|
||||
# If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
|
||||
# Then we are OK to stop waiting and go ahead
|
||||
# Otherwise go back and wait again
|
||||
c = @Counter.value
|
||||
break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Release a previously acquired write lock.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully released
|
||||
def release_write_lock
|
||||
return true unless running_writer?
|
||||
c = @Counter.update { |counter| counter - RUNNING_WRITER }
|
||||
@ReadLock.broadcast
|
||||
@WriteLock.signal if waiting_writers(c) > 0
|
||||
true
|
||||
end
|
||||
|
||||
# Queries if the write lock is held by any thread.
|
||||
#
|
||||
# @return [Boolean] true if the write lock is held else false`
|
||||
def write_locked?
|
||||
@Counter.value >= RUNNING_WRITER
|
||||
end
|
||||
|
||||
# Queries whether any threads are waiting to acquire the read or write lock.
|
||||
#
|
||||
# @return [Boolean] true if any threads are waiting for a lock else false
|
||||
def has_waiters?
|
||||
waiting_writer?(@Counter.value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @!visibility private
|
||||
def running_readers(c = @Counter.value)
|
||||
c & MAX_READERS
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def running_readers?(c = @Counter.value)
|
||||
(c & MAX_READERS) > 0
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def running_writer?(c = @Counter.value)
|
||||
c >= RUNNING_WRITER
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def waiting_writers(c = @Counter.value)
|
||||
(c & MAX_WRITERS) / WAITING_WRITER
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def waiting_writer?(c = @Counter.value)
|
||||
c >= WAITING_WRITER
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def max_readers?(c = @Counter.value)
|
||||
(c & MAX_READERS) == MAX_READERS
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def max_writers?(c = @Counter.value)
|
||||
(c & MAX_WRITERS) == MAX_WRITERS
|
||||
end
|
||||
end
|
||||
end
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
require 'thread'
|
||||
require 'concurrent/atomic/atomic_reference'
|
||||
require 'concurrent/atomic/atomic_fixnum'
|
||||
require 'concurrent/errors'
|
||||
require 'concurrent/synchronization/object'
|
||||
require 'concurrent/synchronization/lock'
|
||||
require 'concurrent/atomic/lock_local_var'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# Re-entrant read-write lock implementation
|
||||
#
|
||||
# Allows any number of concurrent readers, but only one concurrent writer
|
||||
# (And while the "write" lock is taken, no read locks can be obtained either.
|
||||
# Hence, the write lock can also be called an "exclusive" lock.)
|
||||
#
|
||||
# If another thread has taken a read lock, any thread which wants a write lock
|
||||
# will block until all the readers release their locks. However, once a thread
|
||||
# starts waiting to obtain a write lock, any additional readers that come along
|
||||
# will also wait (so writers are not starved).
|
||||
#
|
||||
# A thread can acquire both a read and write lock at the same time. A thread can
|
||||
# also acquire a read lock OR a write lock more than once. Only when the read (or
|
||||
# write) lock is released as many times as it was acquired, will the thread
|
||||
# actually let it go, allowing other threads which might have been waiting
|
||||
# to proceed. Therefore the lock can be upgraded by first acquiring
|
||||
# read lock and then write lock and that the lock can be downgraded by first
|
||||
# having both read and write lock a releasing just the write lock.
|
||||
#
|
||||
# If both read and write locks are acquired by the same thread, it is not strictly
|
||||
# necessary to release them in the same order they were acquired. In other words,
|
||||
# the following code is legal:
|
||||
#
|
||||
# @example
|
||||
# lock = Concurrent::ReentrantReadWriteLock.new
|
||||
# lock.acquire_write_lock
|
||||
# lock.acquire_read_lock
|
||||
# lock.release_write_lock
|
||||
# # At this point, the current thread is holding only a read lock, not a write
|
||||
# # lock. So other threads can take read locks, but not a write lock.
|
||||
# lock.release_read_lock
|
||||
# # Now the current thread is not holding either a read or write lock, so
|
||||
# # another thread could potentially acquire a write lock.
|
||||
#
|
||||
# This implementation was inspired by `java.util.concurrent.ReentrantReadWriteLock`.
|
||||
#
|
||||
# @example
|
||||
# lock = Concurrent::ReentrantReadWriteLock.new
|
||||
# lock.with_read_lock { data.retrieve }
|
||||
# lock.with_write_lock { data.modify! }
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock
|
||||
class ReentrantReadWriteLock < Synchronization::Object
|
||||
|
||||
# Implementation notes:
|
||||
#
|
||||
# A goal is to make the uncontended path for both readers/writers mutex-free
|
||||
# Only if there is reader-writer or writer-writer contention, should mutexes be used
|
||||
# Otherwise, a single CAS operation is all we need to acquire/release a lock
|
||||
#
|
||||
# Internal state is represented by a single integer ("counter"), and updated
|
||||
# using atomic compare-and-swap operations
|
||||
# When the counter is 0, the lock is free
|
||||
# Each thread which has one OR MORE read locks increments the counter by 1
|
||||
# (and decrements by 1 when releasing the read lock)
|
||||
# The counter is increased by (1 << 15) for each writer waiting to acquire the
|
||||
# write lock, and by (1 << 29) if the write lock is taken
|
||||
#
|
||||
# Additionally, each thread uses a thread-local variable to count how many times
|
||||
# it has acquired a read lock, AND how many times it has acquired a write lock.
|
||||
# It uses a similar trick; an increment of 1 means a read lock was taken, and
|
||||
# an increment of (1 << 15) means a write lock was taken
|
||||
# This is what makes re-entrancy possible
|
||||
#
|
||||
# 2 rules are followed to ensure good liveness properties:
|
||||
# 1) Once a writer has queued up and is waiting for a write lock, no other thread
|
||||
# can take a lock without waiting
|
||||
# 2) When a write lock is released, readers are given the "first chance" to wake
|
||||
# up and acquire a read lock
|
||||
# Following these rules means readers and writers tend to "take turns", so neither
|
||||
# can starve the other, even under heavy contention
|
||||
|
||||
# @!visibility private
|
||||
READER_BITS = 15
|
||||
# @!visibility private
|
||||
WRITER_BITS = 14
|
||||
|
||||
# Used with @Counter:
|
||||
# @!visibility private
|
||||
WAITING_WRITER = 1 << READER_BITS
|
||||
# @!visibility private
|
||||
RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
|
||||
# @!visibility private
|
||||
MAX_READERS = WAITING_WRITER - 1
|
||||
# @!visibility private
|
||||
MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1
|
||||
|
||||
# Used with @HeldCount:
|
||||
# @!visibility private
|
||||
WRITE_LOCK_HELD = 1 << READER_BITS
|
||||
# @!visibility private
|
||||
READ_LOCK_MASK = WRITE_LOCK_HELD - 1
|
||||
# @!visibility private
|
||||
WRITE_LOCK_MASK = MAX_WRITERS
|
||||
|
||||
safe_initialization!
|
||||
|
||||
# Create a new `ReentrantReadWriteLock` in the unlocked state.
|
||||
def initialize
|
||||
super()
|
||||
@Counter = AtomicFixnum.new(0) # single integer which represents lock state
|
||||
@ReadQueue = Synchronization::Lock.new # used to queue waiting readers
|
||||
@WriteQueue = Synchronization::Lock.new # used to queue waiting writers
|
||||
@HeldCount = LockLocalVar.new(0) # indicates # of R & W locks held by this thread
|
||||
end
|
||||
|
||||
# Execute a block operation within a read lock.
|
||||
#
|
||||
# @yield the task to be performed within the lock.
|
||||
#
|
||||
# @return [Object] the result of the block operation.
|
||||
#
|
||||
# @raise [ArgumentError] when no block is given.
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def with_read_lock
|
||||
raise ArgumentError.new('no block given') unless block_given?
|
||||
acquire_read_lock
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release_read_lock
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a block operation within a write lock.
|
||||
#
|
||||
# @yield the task to be performed within the lock.
|
||||
#
|
||||
# @return [Object] the result of the block operation.
|
||||
#
|
||||
# @raise [ArgumentError] when no block is given.
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def with_write_lock
|
||||
raise ArgumentError.new('no block given') unless block_given?
|
||||
acquire_write_lock
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
release_write_lock
|
||||
end
|
||||
end
|
||||
|
||||
# Acquire a read lock. If a write lock is held by another thread, will block
|
||||
# until it is released.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
#
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of readers
|
||||
# is exceeded.
|
||||
def acquire_read_lock
|
||||
if (held = @HeldCount.value) > 0
|
||||
# If we already have a lock, there's no need to wait
|
||||
if held & READ_LOCK_MASK == 0
|
||||
# But we do need to update the counter, if we were holding a write
|
||||
# lock but not a read lock
|
||||
@Counter.update { |c| c + 1 }
|
||||
end
|
||||
@HeldCount.value = held + 1
|
||||
return true
|
||||
end
|
||||
|
||||
while true
|
||||
c = @Counter.value
|
||||
raise ResourceLimitError.new('Too many reader threads') if max_readers?(c)
|
||||
|
||||
# If a writer is waiting OR running when we first queue up, we need to wait
|
||||
if waiting_or_running_writer?(c)
|
||||
# Before going to sleep, check again with the ReadQueue mutex held
|
||||
@ReadQueue.synchronize do
|
||||
@ReadQueue.ns_wait if waiting_or_running_writer?
|
||||
end
|
||||
# Note: the above 'synchronize' block could have used #wait_until,
|
||||
# but that waits repeatedly in a loop, checking the wait condition
|
||||
# each time it wakes up (to protect against spurious wakeups)
|
||||
# But we are already in a loop, which is only broken when we successfully
|
||||
# acquire the lock! So we don't care about spurious wakeups, and would
|
||||
# rather not pay the extra overhead of using #wait_until
|
||||
|
||||
# After a reader has waited once, they are allowed to "barge" ahead of waiting writers
|
||||
# But if a writer is *running*, the reader still needs to wait (naturally)
|
||||
while true
|
||||
c = @Counter.value
|
||||
if running_writer?(c)
|
||||
@ReadQueue.synchronize do
|
||||
@ReadQueue.ns_wait if running_writer?
|
||||
end
|
||||
elsif @Counter.compare_and_set(c, c+1)
|
||||
@HeldCount.value = held + 1
|
||||
return true
|
||||
end
|
||||
end
|
||||
elsif @Counter.compare_and_set(c, c+1)
|
||||
@HeldCount.value = held + 1
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Try to acquire a read lock and return true if we succeed. If it cannot be
|
||||
# acquired immediately, return false.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
def try_read_lock
|
||||
if (held = @HeldCount.value) > 0
|
||||
if held & READ_LOCK_MASK == 0
|
||||
# If we hold a write lock, but not a read lock...
|
||||
@Counter.update { |c| c + 1 }
|
||||
end
|
||||
@HeldCount.value = held + 1
|
||||
return true
|
||||
else
|
||||
c = @Counter.value
|
||||
if !waiting_or_running_writer?(c) && @Counter.compare_and_set(c, c+1)
|
||||
@HeldCount.value = held + 1
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Release a previously acquired read lock.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully released
|
||||
def release_read_lock
|
||||
held = @HeldCount.value = @HeldCount.value - 1
|
||||
rlocks_held = held & READ_LOCK_MASK
|
||||
if rlocks_held == 0
|
||||
c = @Counter.update { |counter| counter - 1 }
|
||||
# If one or more writers were waiting, and we were the last reader, wake a writer up
|
||||
if waiting_or_running_writer?(c) && running_readers(c) == 0
|
||||
@WriteQueue.signal
|
||||
end
|
||||
elsif rlocks_held == READ_LOCK_MASK
|
||||
raise IllegalOperationError, "Cannot release a read lock which is not held"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Acquire a write lock. Will block and wait for all active readers and writers.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
#
|
||||
# @raise [Concurrent::ResourceLimitError] if the maximum number of writers
|
||||
# is exceeded.
|
||||
def acquire_write_lock
|
||||
if (held = @HeldCount.value) >= WRITE_LOCK_HELD
|
||||
# if we already have a write (exclusive) lock, there's no need to wait
|
||||
@HeldCount.value = held + WRITE_LOCK_HELD
|
||||
return true
|
||||
end
|
||||
|
||||
while true
|
||||
c = @Counter.value
|
||||
raise ResourceLimitError.new('Too many writer threads') if max_writers?(c)
|
||||
|
||||
# To go ahead and take the lock without waiting, there must be no writer
|
||||
# running right now, AND no writers who came before us still waiting to
|
||||
# acquire the lock
|
||||
# Additionally, if any read locks have been taken, we must hold all of them
|
||||
if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER)
|
||||
# If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead
|
||||
@HeldCount.value = held + WRITE_LOCK_HELD
|
||||
return true
|
||||
elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
|
||||
while true
|
||||
# Now we have successfully incremented, so no more readers will be able to increment
|
||||
# (they will wait instead)
|
||||
# However, readers OR writers could decrement right here
|
||||
@WriteQueue.synchronize do
|
||||
# So we have to do another check inside the synchronized section
|
||||
# If a writer OR another reader is running, then go to sleep
|
||||
c = @Counter.value
|
||||
@WriteQueue.ns_wait if running_writer?(c) || running_readers(c) != held
|
||||
end
|
||||
# Note: if you are thinking of replacing the above 'synchronize' block
|
||||
# with #wait_until, read the comment in #acquire_read_lock first!
|
||||
|
||||
# We just came out of a wait
|
||||
# If we successfully turn the RUNNING_WRITER bit on with an atomic swap,
|
||||
# then we are OK to stop waiting and go ahead
|
||||
# Otherwise go back and wait again
|
||||
c = @Counter.value
|
||||
if !running_writer?(c) &&
|
||||
running_readers(c) == held &&
|
||||
@Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER)
|
||||
@HeldCount.value = held + WRITE_LOCK_HELD
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Try to acquire a write lock and return true if we succeed. If it cannot be
|
||||
# acquired immediately, return false.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully acquired
|
||||
def try_write_lock
|
||||
if (held = @HeldCount.value) >= WRITE_LOCK_HELD
|
||||
@HeldCount.value = held + WRITE_LOCK_HELD
|
||||
return true
|
||||
else
|
||||
c = @Counter.value
|
||||
if !waiting_or_running_writer?(c) &&
|
||||
running_readers(c) == held &&
|
||||
@Counter.compare_and_set(c, c+RUNNING_WRITER)
|
||||
@HeldCount.value = held + WRITE_LOCK_HELD
|
||||
return true
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Release a previously acquired write lock.
|
||||
#
|
||||
# @return [Boolean] true if the lock is successfully released
|
||||
def release_write_lock
|
||||
held = @HeldCount.value = @HeldCount.value - WRITE_LOCK_HELD
|
||||
wlocks_held = held & WRITE_LOCK_MASK
|
||||
if wlocks_held == 0
|
||||
c = @Counter.update { |counter| counter - RUNNING_WRITER }
|
||||
@ReadQueue.broadcast
|
||||
@WriteQueue.signal if waiting_writers(c) > 0
|
||||
elsif wlocks_held == WRITE_LOCK_MASK
|
||||
raise IllegalOperationError, "Cannot release a write lock which is not held"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @!visibility private
|
||||
def running_readers(c = @Counter.value)
|
||||
c & MAX_READERS
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def running_readers?(c = @Counter.value)
|
||||
(c & MAX_READERS) > 0
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def running_writer?(c = @Counter.value)
|
||||
c >= RUNNING_WRITER
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def waiting_writers(c = @Counter.value)
|
||||
(c & MAX_WRITERS) >> READER_BITS
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def waiting_or_running_writer?(c = @Counter.value)
|
||||
c >= WAITING_WRITER
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def max_readers?(c = @Counter.value)
|
||||
(c & MAX_READERS) == MAX_READERS
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def max_writers?(c = @Counter.value)
|
||||
(c & MAX_WRITERS) == MAX_WRITERS
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,163 @@
|
||||
require 'concurrent/atomic/mutex_semaphore'
|
||||
|
||||
module Concurrent
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro semaphore_method_initialize
|
||||
#
|
||||
# Create a new `Semaphore` with the initial `count`.
|
||||
#
|
||||
# @param [Fixnum] count the initial count
|
||||
#
|
||||
# @raise [ArgumentError] if `count` is not an integer
|
||||
|
||||
# @!macro semaphore_method_acquire
|
||||
#
|
||||
# Acquires the given number of permits from this semaphore,
|
||||
# blocking until all are available. If a block is given,
|
||||
# yields to it and releases the permits afterwards.
|
||||
#
|
||||
# @param [Fixnum] permits Number of permits to acquire
|
||||
#
|
||||
# @raise [ArgumentError] if `permits` is not an integer or is less than zero
|
||||
#
|
||||
# @return [nil, BasicObject] Without a block, `nil` is returned. If a block
|
||||
# is given, its return value is returned.
|
||||
|
||||
# @!macro semaphore_method_available_permits
|
||||
#
|
||||
# Returns the current number of permits available in this semaphore.
|
||||
#
|
||||
# @return [Integer]
|
||||
|
||||
# @!macro semaphore_method_drain_permits
|
||||
#
|
||||
# Acquires and returns all permits that are immediately available.
|
||||
#
|
||||
# @return [Integer]
|
||||
|
||||
# @!macro semaphore_method_try_acquire
|
||||
#
|
||||
# Acquires the given number of permits from this semaphore,
|
||||
# only if all are available at the time of invocation or within
|
||||
# `timeout` interval. If a block is given, yields to it if the permits
|
||||
# were successfully acquired, and releases them afterward, returning the
|
||||
# block's return value.
|
||||
#
|
||||
# @param [Fixnum] permits the number of permits to acquire
|
||||
#
|
||||
# @param [Fixnum] timeout the number of seconds to wait for the counter
|
||||
# or `nil` to return immediately
|
||||
#
|
||||
# @raise [ArgumentError] if `permits` is not an integer or is less than zero
|
||||
#
|
||||
# @return [true, false, nil, BasicObject] `false` if no permits are
|
||||
# available, `true` when acquired a permit. If a block is given, the
|
||||
# block's return value is returned if the permits were acquired; if not,
|
||||
# `nil` is returned.
|
||||
|
||||
# @!macro semaphore_method_release
|
||||
#
|
||||
# Releases the given number of permits, returning them to the semaphore.
|
||||
#
|
||||
# @param [Fixnum] permits Number of permits to return to the semaphore.
|
||||
#
|
||||
# @raise [ArgumentError] if `permits` is not a number or is less than zero
|
||||
#
|
||||
# @return [nil]
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!macro semaphore_public_api
|
||||
#
|
||||
# @!method initialize(count)
|
||||
# @!macro semaphore_method_initialize
|
||||
#
|
||||
# @!method acquire(permits = 1)
|
||||
# @!macro semaphore_method_acquire
|
||||
#
|
||||
# @!method available_permits
|
||||
# @!macro semaphore_method_available_permits
|
||||
#
|
||||
# @!method drain_permits
|
||||
# @!macro semaphore_method_drain_permits
|
||||
#
|
||||
# @!method try_acquire(permits = 1, timeout = nil)
|
||||
# @!macro semaphore_method_try_acquire
|
||||
#
|
||||
# @!method release(permits = 1)
|
||||
# @!macro semaphore_method_release
|
||||
|
||||
###################################################################
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
SemaphoreImplementation = if Concurrent.on_jruby?
|
||||
require 'concurrent/utility/native_extension_loader'
|
||||
JavaSemaphore
|
||||
else
|
||||
MutexSemaphore
|
||||
end
|
||||
private_constant :SemaphoreImplementation
|
||||
|
||||
# @!macro semaphore
|
||||
#
|
||||
# A counting semaphore. Conceptually, a semaphore maintains a set of
|
||||
# permits. Each {#acquire} blocks if necessary until a permit is
|
||||
# available, and then takes it. Each {#release} adds a permit, potentially
|
||||
# releasing a blocking acquirer.
|
||||
# However, no actual permit objects are used; the Semaphore just keeps a
|
||||
# count of the number available and acts accordingly.
|
||||
# Alternatively, permits may be acquired within a block, and automatically
|
||||
# released after the block finishes executing.
|
||||
#
|
||||
# @!macro semaphore_public_api
|
||||
# @example
|
||||
# semaphore = Concurrent::Semaphore.new(2)
|
||||
#
|
||||
# t1 = Thread.new do
|
||||
# semaphore.acquire
|
||||
# puts "Thread 1 acquired semaphore"
|
||||
# end
|
||||
#
|
||||
# t2 = Thread.new do
|
||||
# semaphore.acquire
|
||||
# puts "Thread 2 acquired semaphore"
|
||||
# end
|
||||
#
|
||||
# t3 = Thread.new do
|
||||
# semaphore.acquire
|
||||
# puts "Thread 3 acquired semaphore"
|
||||
# end
|
||||
#
|
||||
# t4 = Thread.new do
|
||||
# sleep(2)
|
||||
# puts "Thread 4 releasing semaphore"
|
||||
# semaphore.release
|
||||
# end
|
||||
#
|
||||
# [t1, t2, t3, t4].each(&:join)
|
||||
#
|
||||
# # prints:
|
||||
# # Thread 3 acquired semaphore
|
||||
# # Thread 2 acquired semaphore
|
||||
# # Thread 4 releasing semaphore
|
||||
# # Thread 1 acquired semaphore
|
||||
#
|
||||
# @example
|
||||
# semaphore = Concurrent::Semaphore.new(1)
|
||||
#
|
||||
# puts semaphore.available_permits
|
||||
# semaphore.acquire do
|
||||
# puts semaphore.available_permits
|
||||
# end
|
||||
# puts semaphore.available_permits
|
||||
#
|
||||
# # prints:
|
||||
# # 1
|
||||
# # 0
|
||||
# # 1
|
||||
class Semaphore < SemaphoreImplementation
|
||||
end
|
||||
end
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
require 'concurrent/constants'
|
||||
require_relative 'locals'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# A `ThreadLocalVar` is a variable where the value is different for each thread.
|
||||
# Each variable may have a default value, but when you modify the variable only
|
||||
# the current thread will ever see that change.
|
||||
#
|
||||
# This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`),
|
||||
# but with these major advantages:
|
||||
# * `ThreadLocalVar` has its own identity, it doesn't need a Symbol.
|
||||
# * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread),
|
||||
# so it's only OK to create a small amount of them.
|
||||
# `ThreadLocalVar` has no such issue and it is fine to create many of them.
|
||||
# * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly).
|
||||
# `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd.
|
||||
#
|
||||
# @!macro thread_safe_variable_comparison
|
||||
#
|
||||
# @example
|
||||
# v = ThreadLocalVar.new(14)
|
||||
# v.value #=> 14
|
||||
# v.value = 2
|
||||
# v.value #=> 2
|
||||
#
|
||||
# @example
|
||||
# v = ThreadLocalVar.new(14)
|
||||
#
|
||||
# t1 = Thread.new do
|
||||
# v.value #=> 14
|
||||
# v.value = 1
|
||||
# v.value #=> 1
|
||||
# end
|
||||
#
|
||||
# t2 = Thread.new do
|
||||
# v.value #=> 14
|
||||
# v.value = 2
|
||||
# v.value #=> 2
|
||||
# end
|
||||
#
|
||||
# v.value #=> 14
|
||||
class ThreadLocalVar
|
||||
LOCALS = ThreadLocals.new
|
||||
|
||||
# Creates a thread local variable.
|
||||
#
|
||||
# @param [Object] default the default value when otherwise unset
|
||||
# @param [Proc] default_block Optional block that gets called to obtain the
|
||||
# default value for each thread
|
||||
def initialize(default = nil, &default_block)
|
||||
if default && block_given?
|
||||
raise ArgumentError, "Cannot use both value and block as default value"
|
||||
end
|
||||
|
||||
if block_given?
|
||||
@default_block = default_block
|
||||
@default = nil
|
||||
else
|
||||
@default_block = nil
|
||||
@default = default
|
||||
end
|
||||
|
||||
@index = LOCALS.next_index(self)
|
||||
end
|
||||
|
||||
# Returns the value in the current thread's copy of this thread-local variable.
|
||||
#
|
||||
# @return [Object] the current value
|
||||
def value
|
||||
LOCALS.fetch(@index) { default }
|
||||
end
|
||||
|
||||
# Sets the current thread's copy of this thread-local variable to the specified value.
|
||||
#
|
||||
# @param [Object] value the value to set
|
||||
# @return [Object] the new value
|
||||
def value=(value)
|
||||
LOCALS.set(@index, value)
|
||||
end
|
||||
|
||||
# Bind the given value to thread local storage during
|
||||
# execution of the given block.
|
||||
#
|
||||
# @param [Object] value the value to bind
|
||||
# @yield the operation to be performed with the bound variable
|
||||
# @return [Object] the value
|
||||
def bind(value)
|
||||
if block_given?
|
||||
old_value = self.value
|
||||
self.value = value
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
self.value = old_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def default
|
||||
if @default_block
|
||||
self.value = @default_block.call
|
||||
else
|
||||
@default
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user