Issue #8259 has been updated by Vit Z.


Petr Chalupa wrote:
> it certainly looks appealing and convenient to have atomic ivars, but I have an devil advocate's question. What are the advantages of having atomic_attr over just using `@a = Atomic.new('a_value')` with some convenience methods like `def a_swap(v); @a.swap(v); end` added?  Is it a performance?

Performance and memory efficiency, plus some minor issues with pre-initialization
(in your example `@a = Atomic.new`) in constructor and safe-publication issues.

> To me using Atomic seems perfectly fine, I like clarity without confusion. It does not seem right that reading a `@var` would behave differently depending on `attr_reader atomic: true||false`.

Note that by "behave differently" you mean "will not break under concurrent access".
Besides I would like to point you at Java experience, where `volatile` (Java's "atomic"
i-vars) and normal instance variables also look the same `this.foo; vs this.foo;`, I'm
not aware that this is creating any problems.

### On reference equality only, rather than value equality

I would like to echo Dirkjan Bussink's experience from his post above, when it comes
to CAS - one usually reads the old value and then tries to CAS in a new value (using
the previously read value), in such use cases reference vs value equality is not an issue.

The other typical CAS use case are state machines, where "atomic" values are integers
representing a state:

~~~
class ReadWriteLock
  def initialize
    # @state == 0 means unlocked
    # @state == 1 means read locked
    # @state == 2 means write locked
    @state = 0
  end

  def lock_for_read!
    cas_state(0, 1)
  end

  def lock_for_write!
    cas_state(0, 2)
  end
  
  def read_locked?
    @state == 1
  end
  
  def write_locked?
    @state == 2
  end
end
~~~

In such a use case, CAS reference equality would not work (sidestepping that JRuby
pools some small range of Fixnums, I think it is from -127 to 127 or something).

This can be fixed by storing states in constants, this also improves readability:

~~~
class ReadWriteLock
  NOT_LOCKED   = 0
  READ_LOCKED  = 1
  WRITE_LOCKED = 2
  
  def initialize
    @state = NOT_LOCKED
  end

  def lock_for_read!
    cas_state(NOT_LOCKED, READ_LOCKED)
  end

  def lock_for_write!
    cas_state(NOT_LOCKED, WRITE_LOCKED)
  end
  
  def read_locked?
    @state == READ_LOCKED
  end
  
  def write_locked?
    @state == WRITE_LOCKED
  end
end
~~~

However this is Ruby and in Ruby we have symbols, that are guaranteed to be singletons:

~~~
class ReadWriteLock
  def initialize
    @state = :unlocked
  end

  def lock_for_read!
    cas_state(:unlocked, :read_locked)
  end

  def lock_for_write!
    cas_state(:unlocked, :write_locked)
  end
  
  def read_locked?
    @state == :read_locked
  end
  
  def write_locked?
    @state == :write_locked
  end
end
~~~

So in my opinion a pure raw-pointer/reference CAS implementation does not impede
any of usual CAS use cases.


----------------------------------------
Feature #8259: Atomic attributes accessors
https://bugs.ruby-lang.org/issues/8259#change-47227

* Author: Yura Sokolov
* Status: Open
* Priority: Normal
* Assignee: 
* Category: 
* Target version: Ruby 2.1.0
----------------------------------------
=begin
Motivated by this gist ((<URL:https://gist.github.com/jstorimer/5298581>)) and atomic gem

I propose Class.attr_atomic which will add methods for atomic swap and CAS:

  class MyNode
    attr_accessor :item
    attr_atomic :successor

    def initialize(item, successor)
      @item = item
      @successor = successor
    end
  end
  node = MyNode.new(i, other_node)

  # attr_atomic ensures at least #{attr} reader method exists. May be, it should
  # be sure it does volatile access.
  node.successor

  # #{attr}_cas(old_value, new_value) do CAS: atomic compare and swap
  if node.successor_cas(other_node, new_node)
    print "there were no interleaving with other threads"
  end

  # #{attr}_swap atomically swaps value and returns old value.
  # It ensures that no other thread interleaves getting old value and setting
  # new one by cas (or other primitive if exists, like in Java 8)
  node.successor_swap(new_node)

It will be very simple for MRI cause of GIL, and it will use atomic primitives for
other implementations.

Note: both (({#{attr}_swap})) and (({#{attr}_cas})) should raise an error if instance variable were not explicitly set before.

Example for nonblocking queue: ((<URL:https://gist.github.com/funny-falcon/5370416>))

Something similar should be proposed for Structs. May be override same method as (({Struct.attr_atomic}))

Open question for reader:
should (({attr_atomic :my_attr})) ensure that #my_attr reader method exists?
Should it guarantee that (({#my_attr})) provides 'volatile' access?
May be, (({attr_reader :my_attr})) already ought to provide 'volatile' semantic?
May be, semantic of (({@my_attr})) should have volatile semantic (i doubt for that)?
=end




-- 
https://bugs.ruby-lang.org/