Листинг 13.1. Программа индексирования с ошибками (гонка)
@list = []
@list[0]="shoes ships\nsealing-wax"
@list[1]="cabbages kings"
@list[2]="quarks\nships\ncabbages"
def hesitate
sleep rand(0)
end
@hash = {}
def process_list(listnum)
lnum = 0
@list[listnum].each do |line|
words = line.chomp.split
words.each do |w|
hesitate
if @hash[w]
hesitate
@hash[w] += ["#{listnum}:#{lnum}"]
else
hesitate
@hash[w] = ["#{listnum}:#{lnum}"]
end
end
lnum += 1
end
end
t1 = Thread.new(0) {|num| process_list(num) }
t2 = Thread.new(1) {|num| process_list(num) }
t3 = Thread.new(2) {|num| process_list(num) }
t1.join
t2.join
t3.join
count = 0
@hash.values.each {|v| count += v.size }
puts "Всего
слов: #{count} " # Может быть напечатано 7 или 8!
Здесь имеется проблема. Если ваша система ведет себя примерно так же, как наша, то программа может напечатать одно из двух значений! В наших тестах с одинаковой вероятностью печаталось 7 или 8. Если слов и списков больше, то и разброс окажется более широким.
Попробуем исправить положение с помощью мьютекса, который будет контролировать доступ к разделяемому ресурсу. (Слово «mutex» — это сокращение от mutual exclusion, «взаимная блокировка».)
Обратимся к листингу 13.2. Библиотека
Mutex
позволяет создавать мьютексы и манипулировать ими. Мы можем захватить (lock) мьютекс перед доступом к хэшу и освободить (unlock) его по завершении операции.
Листинг 13.2. Программа индексирования с мьютексом
require 'thread.rb'
@list = []
@list[0]="shoes ships\nsealing-wax"
@list[1]="cabbages kings"
@list[2]="quarks\nships\ncabbages"
def hesitate
sleep rand(0)
end
@hash = {}
@mutex = Mutex.new
def process_list(listnum)
lnum = 0
@list[listnum].each do |line|
words = line.chomp.split
words.each do |w|
hesitate
@mutex.lock
if @hash[w]
hesitate
@hash[w] += ["#{listnum}:#{lnum}"]
else
hesitate
@hash[w] = ["#{listnum}:#{lnum}"]
end
@mutex.unlock
end
lnum += 1
end
end
t1 = Thread.new(0) {|num| process_list(num) }
t2 = Thread.new(1) {|num| process_list(num) }
t3 = Thread.new(2) {|num| process_list(num) }
t1.join
t2.join
t3.join
count = 0
@hash.values.each {|v| count += v.size }
puts "Всего
слов: #{count} " # Всегда печатается 8!
Отметим, что помимо метода
lock
в классе
Mutex
есть также метод
try_lock
. Он отличается от
lock
тем, что если мьютекс уже захвачен другим потоком, то он не дожидается освобождения, а сразу возвращает
false
.
require 'thread'
mutex = Mutex.new
t1 = Thread.new { mutex.lock; sleep 30 }
sleep 1
t2 = Thread.new do
if mutex.try_lock
puts "Захватил"
else
puts "He сумел захватить" # Печатается немедленно.
end
end
sleep 2
Эта возможность полезна, если поток не хочет приостанавливать выполнение. Есть также метод
synchronize
, который захватывает мьютекс, а потом автоматически освобождает его.
mutex = Mutex.new
mutex.synchronize do
# Любой код, нуждающийся в защите...
end
Существует еще библиотека
mutex_m
, где определен модуль
Mutex_m
, который можно подмешивать к классу (или использовать для расширения объекта). У такого расширенного объекта будут все методы мьютекса, так что он сам может выступать в роли мьютекса.
require 'mutex_m'
class MyClass
include Mutex_m
# Теперь любой объект класса MyClass может вызывать