When you are sharing objects in multi-threaded processing, multiple threads can modify the same object at the same time, possibly in an inconsistent state, and be aware of it when used.
Example:
Test.rb
x = 010.times.map do |i| Thread.new do puts "before (#{i}): #{x}" x + = 1 puts "after (#{i}): #{x}" Endend.each (&:join) p UTS "\ntotal: #{x}"
Execute Ruby TEST.RB
Output:
Before (1): 0after (1): 1before (0): 1after (0): 2before (2): 2after (2): 3before (4): 3after (4): 4before (6): 4before (5 ): 3before (9): 3before (8): 3before (7): 3after (7): 6before (3): 3after (3): 7after (9): 5after (8): 8after (6): 9after (5): 10total:10
You may have noticed that in the fourth cycle, I = 3, the output:
Before (3): 3 #x等于3
After (3): 7 #x等于7
A mutex is a class that implements a simple signal lock for mutual access of some shared resources. In other words, only one thread can hold a lock at a given time. Other threads may choose to wait for the lock line to become available, or may simply choose an error that indicates that a lock cannot be used immediately.
By sharing the data under a mutually exclusive control of all accesses, we can ensure consistency and atomic manipulation, placing the code in the Synchorize method of the mutex object.
Modify the contents of the Test.rb file as follows:
x = 0mutex = Mutex.new10.times.map do |i| Thread.new do mutex.synchronize do puts "before (#{i}): #{x}" x + = 1 puts "After (#{i}): #{x}" End Endend.each (&:join) puts "\ntotal: #{x}"
Execute Ruby TEST.RB
Output:
Before (0): 0after (0): 1before (9): 1after (9): 2before (2): 2after (2): 3before (3): 3after (3): 4before (4): 4after (4) : 5before (5): 5after (5): 6before (6): 6after (6): 7before (7): 7after (7): 8before (8): 8after (8): 9before (1): 9after (1): 10total:10
Here is an example of a counter:
The contents of the App.rb file are as follows:
Class Counter attr_reader:total def initialize puts ' initialized ... ' @total = 0 @mutex = Mutex.new End def increment! @mutex. Synchronize {@total + = 1} endendclass application def counter @counter | | = Counter.new End def increment! counter.increment! End def total counter.total Endendapp = Application.new10.times.map do |i| Thread.new do app.increment! Endend.each (&:join) puts App.total
Performing Ruby app.rb Sometimes results in this:
Initialized...initialized...initialized...initialized...initialized...initialized...initialized...initialized ..... initialized ... 1
This result is wrong, counter uses a thread, but the final application is not, because we use | | =, it is not atomic. Application two instances see the @counter are nil, so all are instantiated counter, so the result is wrong.
The correct thing is to change the application to this:
Class Application def initialize @counter = counter.new End def counter @counter End def increment! counter.increment! End def total counter.total endend
This will instantiate the counter when the application is instantiated.
Executed again, the results are as follows:
Initialized ... 10
Handling Deadlocks:
When we start using mutex objects for thread repulsion, we must be careful to avoid deadlocks. A deadlock condition occurs when all threads are waiting to acquire resources held by another thread. Because all the threads are blocked, they cannot release the locks they hold. No other threads can acquire these locks because they cannot release the locks.
A condition variable is a simple related resource and a signal that is within the scope of protection for a particular mutex. When the resource you need is unavailable, you wait for a condition variable. This action frees the corresponding mutex. When other threads signal that the resource is available, the original thread waits, while resuming the lock on the critical section.
The Conditionvariable class implements the function of a state variable that supports thread synchronization. A Conditionvariable object is a product that materializes the waiting conditions of a thread.
Mutex = MUTEX.NEWCV = ConditionVariable.newThread.start { mutex.synchronize { ... While (when conditions are not met) cv.wait (m) end ... }}
As shown above, if a thread has not satisfied the condition, call the wait method to suspend it and have the other thread execute
Thread.Start { mutex.synchronize { # does some action to satisfy the above conditions cv.signal }}
Then call the signal method to notify the waiting thread that the above conditions have been established. This is a more typical usage.
Reference: http://lucaguidi.com/2014/03/27/thread-safety-with-ruby.html
Thread Safety of Ruby Multi-threaded shared objects