From: thedarkone2@... Date: 2014-06-09T22:23:44+00:00 Subject: [ruby-core:63029] [CommonRuby - Feature #8259] Atomic attributes accessors Issue #8259 has been updated by Vit Z. In my opinion an ideal API would have "atomic" attributes declared at the class level, yet can be used as normal `@foo` i-vars: ~~~ class MyNode atomic :item, :successor def initialize(item, successor) @foo = :foo # this is plain old i-var @item = item # this is atomic @successor = successor # this is atomic puts @foo # this is a non-atomic read puts @item # this is an atomic read end # this creates reader and writer that are atomic, because :item is atomic attr_accessor :item # this is not atomic attr_accessor :foo def cas_new_item(expected_old_item, new_item) # atomic :item also generates a new private cas_item method # that implements compare-and-swap semantics cas_item(expected_old_item, new_item) end class << self # atomic i-vars can also be declared on Modules/Classes atomic :head end @head = nil # cas_head is also added as a private module method cas_head(nil, MyNode.new(:item, nil)) end ~~~ Another thing, and I know that this might be controversial, is that `atomic` attributes should only be "declare-able" before a first instance of the class is created: ~~~ class OtherNode new # this should raise an ArgumentError, since an instance of OtherNode # has already been created atomic :item class << self # not a problem on Module/Class level atomic :head end end ~~~ I believe this is absolutely necessary in order to have a proper and performant implementation of "atomic" on concurrent Ruby VMs. If I turn out to be wrong the restriction can be always be removed, however if it is not in place and I am right, this forever puts an upper-limit constraint on Ruby performance in concurrent environments. The auto-generated `cas_item` methods need to be "dumb" pointer swapping CASes, ie no clever trickery checking wether passed-in arguments are `Fixnum`s or something else, this is again necessary to not hamstring future upper limit on performance. The other thing that is needed is an "atomic" fixed-size array, lets call it `AtomicTuple`. It has to be fixed-size again because otherwise this would create problems for concurrent Ruby VMs. ~~~ tuple = AtomicTuple.new(16) # creates a new 16-slotted atomic array # an atomic read tuple[0] # => nil # an atomic write tuple[0] = :foo # a cas tuple.cas(0, :foo, :bar) # => true tuple[0] # => :bar ~~~ I'm fairly certain that adding this to MRI should be quite easy, because of the GVL all the "atomicness" is already present. For example declaring an i-var atomic (via atomic `:item`) doesn't have to do anything except to add a `cas_item` method, after all i-vars are already atomic. `cas_item` also in the first implementation doesn't have to use native cmpxchg assembly a simple pointer check-then-swap should be fine (as long as this is in C GVL has our back). Eric Wong: Adding "atomic" operations to built in `Array`s, `Hash`es etc. is a very bad idea because that would force that *all* of the methods on `Array`s and `Hash`es be concurrency friendly and would result in irreparable performance problems for truly concurrent Ruby VMs (and this again would put an upper limit on Ruby performance). ---------------------------------------- Feature #8259: Atomic attributes accessors https://bugs.ruby-lang.org/issues/8259#change-47113 * Author: Yura Sokolov * Status: Open * Priority: Normal * Assignee: * Category: * Target version: Ruby 2.1.0 ---------------------------------------- =begin Motivated by this gist (()) 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: (()) 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/