Exclusión Mutua y la Clase Mutex

Race Condition: definición

De la Wikipedia sobre Race_condition

A race condition or race hazard is the behavior of an electronic or software system where the output is dependent on the sequence or timing of other uncontrollable events.

It becomes a bug when events do not happen in the order the programmer intended.

The term originates with the idea of two signals racing each other to influence the output first.

Ejemplo de Race Condition

[~/ruby/threads(master)]$ cat race_condition.rb 
def inc(n) 
  n+1
end
sum = 0
threads = (1..10).map do
  Thread.new do 
    10_000.times do
      sum = inc(sum)
    end
  end 
end
threads.each(&:join)

Ventajas de Usar JRuby Cuando se Usan Threads

JRuby is built upon the JVM. One can make use of "true" native threads which are able to run concurrently with your parent thread (See Parallelism is a Myth in Ruby by Ilya Grigorik on November 13, 2008).

A Global Interpreter Lock (GIL) is a mutual exclusion lock held by a programming language interpreter thread to avoid sharing code that is not thread-safe with other threads.

Some language implementations that implement a Global Interpreter Lock are CPython, the most widely used implementation of Python, and Ruby MRI, the reference implementation of Ruby.

JVM-based equivalents of these languages (Jython and JRuby) do not use Global Interpreter Locks. JRuby is a Ruby implementation allowing us to natively scale our Ruby code across multiple cores. IronPython and IronRuby are implemented on top of Microsoft's Dynamic Language Runtime and also avoid using a GIL.

Also, Rubinius implements native operating system threads for concurrency and has no global interpreter lock (GIL).

Ejecución con JRuby

[~/ruby/threads(master)]$ rvm list

rvm rubies

   jruby-1.7.3 [ x86_64 ]
=* ruby-2.0.0-p247 [ x86_64 ]
   ruby-2.1.0-preview1 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

[~/ruby/threads(master)]$ rvm use jruby-1.7.3
Using /Users/casiano/.rvm/gems/jruby-1.7.3
[~/ruby/threads(master)]$ ruby -v
jruby 1.7.3 (1.9.3p385) 2013-04-17 fffffff on Java HotSpot(TM) 64-Bit Server VM 1.7.0_25-b15 [darwin-x86_64]
[~/ruby/threads(master)]$ ruby race_condition.rb 
28375
[~/ruby/threads(master)]$ ruby race_condition.rb 
25464
[~/ruby/threads(master)]$ ruby race_condition.rb 
23442

Ejecucion con ruby MRI

[~/ruby/threads(master)]$  rvm use 2.1.0-preview1
Using /Users/casiano/.rvm/gems/ruby-2.1.0-preview1
[~/ruby/threads(master)]$ ruby race_condition.rb 
100000
[~/ruby/threads(master)]$ ruby race_condition.rb 
100000
[~/ruby/threads(master)]$

Mutex

We can use the built-in class Mutex to create synchronized regions—areas of code that only one thread may enter at a time.

We resolve problems like these by using a cooperative locking mechanism:

  1. Each thread that wants to access shared data must first lock that data
  2. The lock is represented by a Mutex (short for "mutual exclusion") object
  3. To lock a Mutex, you call its lock method
  4. When you’re done reading or altering the shared data, you call the unlock method of the Mutex
  5. The lock method blocks when called on a Mutex that’s already locked, and it does not return until the caller has successfully obtained a lock

  6. If each thread that accesses the shared data locks and unlocks the Mutex correctly, no thread will see the data in an inconsistent state and we won’t have problems like those we’ve described
  7. Instead of using the lock and unlock methods explicitly, it is more common to use the synchronize method and associate a block with it
  8. synchronize locks the Mutex, runs the code in the block, and then unlocks the Mutex in an ensure clause so that exceptions are properly handled

[~/ruby/threads(master)]$ cat mutex_synchronize.rb 
# Wait for all threads (other than the current thread and
# main thread) to stop running.
# Assumes that no new threads are started while waiting.
def join_all
  main = Thread.main        # The main thread
  current = Thread.current  # The current thread
  all = Thread.list         # All threads still running
  # Now call join on each thread
  all.each {|t| t.join unless t == current or t == main }
end

def inc(n) 
  n+1
end
sum = 0
mutex = Mutex.new
threads = (1..10).map do
  Thread.new do 
    10_000.times do
      mutex.synchronize do
        sum = inc(sum)
      end
    end
  end 
end
join_all
p sum



Subsecciones
Casiano Rodriguez León 2015-06-18