class Concurrent::Atom
Atoms provide a way to manage shared, synchronous, independent state.
An atom is initialized with an initial value and an optional validation proc. At any time the value of the atom can be synchronously and safely changed. If a validator is given at construction then any new value will be checked against the validator and will be rejected if the validator returns false or raises an exception.
There are two ways to change the value of an atom: {#compare_and_set} and {#swap}. The former will set the new value if and only if it validates and the current value matches the new value. The latter will atomically set the new value to the result of running the given block if and only if that value validates.
## Example
“‘ def next_fibonacci(set = nil)
return [0, 1] if set.nil? set + [set[-2..-1].reduce{|sum,x| sum + x }]
end
# create an atom with an initial value atom = Concurrent::Atom.new(next_fibonacci)
# send a few update requests 5.times do
atom.swap{|set| next_fibonacci(set) }
end
# get the current value atom.value #=> [0, 1, 1, 2, 3, 5, 8] “‘
## Observation
Atoms support observers through the {Concurrent::Observable} mixin module. Notification of observers occurs every time the value of the Atom
changes. When notified the observer will receive three arguments: ‘time`, `old_value`, and `new_value`. The `time` argument is the time at which the value change occurred. The `old_value` is the value of the Atom
when the change began The `new_value` is the value to which the Atom
was set when the change completed. Note that `old_value` and `new_value` may be the same. This is not an error. It simply means that the change operation returned the same value.
Unlike in Clojure, ‘Atom` cannot participate in {Concurrent::TVar} transactions.
@!macro thread_safe_variable_comparison
@see clojure.org/atoms Clojure Atoms @see clojure.org/state Values and Change - Clojure’s approach to Identity and State
Public Class Methods
Create a new atom with the given initial value.
@param [Object] value The initial value @param [Hash] opts The options used to configure the atom @option opts [Proc] :validator (nil) Optional proc used to validate new
values. It must accept one and only one argument which will be the intended new value. The validator will return true if the new value is acceptable else return false (preferrably) or raise an exception.
@!macro deref_options
@raise [ArgumentError] if the validator is not a ‘Proc` (when given)
# File lib/concurrent-ruby/concurrent/atom.rb, line 121 def initialize(value, opts = {}) super() @Validator = opts.fetch(:validator, -> v { true }) self.observers = Collection::CopyOnNotifyObserverSet.new self.value = value end
Public Instance Methods
Atomically sets the value of atom to the new value if and only if the current value of the atom is identical to the old value and the new value successfully validates against the (optional) validator given at construction.
@param [Object] old_value The expected current value. @param [Object] new_value The intended new value.
@return [Boolean] True if the value is changed else false.
# File lib/concurrent-ruby/concurrent/atom.rb, line 181 def compare_and_set(old_value, new_value) if valid?(new_value) && compare_and_set_value(old_value, new_value) observers.notify_observers(Time.now, old_value, new_value) true else false end end
Atomically sets the value of atom to the new value without regard for the current value so long as the new value successfully validates against the (optional) validator given at construction.
@param [Object] new_value The intended new value.
@return [Object] The final value of the atom after all operations and
validations are complete.
# File lib/concurrent-ruby/concurrent/atom.rb, line 198 def reset(new_value) old_value = value if valid?(new_value) self.value = new_value observers.notify_observers(Time.now, old_value, new_value) new_value else old_value end end
Atomically swaps the value of atom using the given block. The current value will be passed to the block, as will any arguments passed as arguments to the function. The new value will be validated against the (optional) validator proc given at construction. If validation fails the value will not be changed.
Internally, {#swap} reads the current value, applies the block to it, and attempts to compare-and-set it in. Since another thread may have changed the value in the intervening time, it may have to retry, and does so in a spin loop. The net effect is that the value will always be the result of the application of the supplied block to a current value, atomically. However, because the block might be called multiple times, it must be free of side effects.
@note The given block may be called multiple times, and thus should be free
of side effects.
@param [Object] args Zero or more arguments passed to the block.
@yield [value, args] Calculates a new value for the atom based on the
current value and any supplied arguments.
@yieldparam value [Object] The current value of the atom. @yieldparam args [Object] All arguments passed to the function, in order. @yieldreturn [Object] The intended new value of the atom.
@return [Object] The final value of the atom after all operations and
validations are complete.
@raise [ArgumentError] When no block is given.
# File lib/concurrent-ruby/concurrent/atom.rb, line 157 def swap(*args) raise ArgumentError.new('no block given') unless block_given? loop do old_value = value new_value = yield(old_value, *args) begin break old_value unless valid?(new_value) break new_value if compare_and_set(old_value, new_value) rescue break old_value end end end
Private Instance Methods
Is the new value valid?
@param [Object] new_value The intended new value. @return [Boolean] false if the validator function returns false or raises
an exception else true
# File lib/concurrent-ruby/concurrent/atom.rb, line 216 def valid?(new_value) @Validator.call(new_value) rescue false end