Search code examples
rubythread-safetyjruby

Class Variables and initialisation time


If I create a class variable like so:

class Song
  @@plays = 0

  class << self

    def plays=( plays )
      @@plays += plays
    end

    def plays
      @@plays
    end

  end

end

An I have multiple threads accessing this class method and setting it in jruby:

t1 = Thread.new {st1 = Song.plays = 1}
t2 = Thread.new {st2 = Song.plays = 5}
t3 = Thread.new {st3 = Song.plays = 3}

Is it possible to have 2 threads initialise @@plays to 0 at the same time? At what stage in execution are class variables created?


Solution

  • @@plays = 0 is set when Ruby evaluates your class definition. This should only happen once and before starting your threads.

    The assignment method plays= on the other hand can be executed concurrently. You should therefore wrap it in a synchronize call, e.g.:

    require 'thread'
    require 'song'   # <- @@plays is set to 0 here
    
    Song.plays #=> 0
    
    semaphore = Mutex.new
    
    t1 = Thread.new { semaphore.synchronize { Song.plays = 1 } }
    t2 = Thread.new { semaphore.synchronize { Song.plays = 5 } }
    t3 = Thread.new { semaphore.synchronize { Song.plays = 3 } }
    
    [t1, t2, t3].each(&:join)
    Song.plays #=> 9
    

    Another option is to make Song#plays= thread-safe by moving the mutex into the Song class:

    class Song
      @@plays = 0
      @@semaphore = Mutex.new
    
      def self.plays=(plays)
        @@semaphore.synchronize { @@plays += plays }
      end
    
      # ...
    end