Книга: Программирование на языке Ruby

13.2.2. Синхронизация доступа к ресурсам (mutex.rb)

13.2.2. Синхронизация доступа к ресурсам (mutex.rb)

В качестве примера рассмотрим задачу индексирования Web-сайтов. Мы извлекаем слова из многочисленных страниц в Сети и сохраняем их в хэше. Ключом является само слово, а значением — строка, идентифицирующая документ и номер строки в этом документе.

Постановка задачи и так достаточно груба. Но мы огрубим ее еще больше, введя следующие упрощающие допущения:

• будем представлять удаленные документы в виде строк;

• ограничимся всего тремя строками (они будут «зашиты» в код);

• сетевые задержки будем моделировать «засыпанием» на случайный промежуток времени.

Взгляните на программу в листинге 13.1. Она даже не печатает получаемые данные целиком, а выводит лишь счетчик слов (не уникальный). Каждый раз при чтении или обновлении хэша мы вызываем метод hesitate, который приостанавливает поток на случайное время. Тем самым поведение программы становится недетерминированным и приближенным к реальности.

Листинг 13.1. Программа индексирования с ошибками (гонка)

@list = []
@list[0]="shoes shipsnsealing-wax"
@list[1]="cabbages kings"
@list[2]="quarksnshipsncabbages"
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 shipsnsealing-wax"
@list[1]="cabbages kings"
@list[2]="quarksnshipsncabbages"
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 может вызывать
 # методы lock, unlock, synchronize...
 # Внешние объекты также могут вызывать эти
 # методы для объекта MyClass.
end

Оглавление книги


Генерация: 1.371. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз