Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Используем потоки в Ruby

Anna | 20.06.2014 | нет комментариев

Многие Руби-разработчики игнорируют потоки (threads), правда это дюже пригодный инструмент. В данной статье мы разглядим создание IO потоков в Руби и покажем как Руби справляется с потоками в которых происходит много вычислительных операций. Испробуем применить альтернативные имплементации Руби, а так же узнаем, каких итогов дозволено добиться при помощи модуля DRb. В конце статьи посмотрим, как эти тезисы применяются в разных серверах для приложений на Ruby on Rails.

IO потоки в Руби

Разглядим маленький пример:

def call_remote(host)
  sleep 3 # симулируем длинный запрос к серверу
end

Если нам нужно обратитьcя к двум серверам, скажем, Дабы очистить кэш, и мы двукратно ступенчато вызовем эту функцию:

call_remote 'host1/clear_caches'
call_remote 'host2/clear_caches'

то наша программа будет трудиться 6 секунд.

Мы можем ускорить исполнение программы, если будем применять потоки, скажем, так:

threads = []

['host1', 'host2'].each do |host|
  threads << Thread.new do
    call_remote "#{host}/clear_caches"
  end
end

threads.each(&:join)

Мы сотворили два потока, в всяком потоке обратились к своему серверу и командами #join сказали, что основной программе (основному потоку) нужно подождать их заключения. Сейчас наша программа удачно выполняется в два раза стремительней, за 3 секунды.

Огромнее потоков отличных и различных

Разглядим больше трудный пример, в котором испробуем заполучить все закрытые баги и задачи с GitHub о плане Jekyll через предоставляемый API.

От того что мы не хотим произвести DoS атаку на GitHub, необходимо ограничить число одновременных потоков, заняться их планированием, запуском и по мере поступления собирать итоги.

Стандартная библиотека Ruby не предоставляет готовых инструментов для решения сходственных задач, следственно я реализовал свою собственную библиотеку FutureProof для создания групп потоков в Ruby, об применении которой и хочу рассказать подробнее.

Ее правило примитивен — нужно сделать новую группу, указав максимально возможное число одновременных потоков:

thread_pool = FutureProof::ThreadPool.new(5)

добавить в него задач:

thread_pool.submit 2, 5 do |a, b|
  a   b
end

и попросить их значения:

thread_pool.values

Таким образом, Дабы получить необходимую нам информацию о плане Jekyll, будет довольно дальнейшего кода:

require 'future_proof'
require 'net/http'

thread_pool = FutureProof::ThreadPool.new(5)
10.times do |i|
  thread_pool.submit i do |page|
    uri = URI.parse(
      "https://api.github.com/repos/mojombo/jekyll/issues?state=close&page=#{page   1}&per_page=100.json"
    )
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.request(Net::HTTP::Get.new(uri.request_uri)).body
  end
end

thread_pool.perform
puts thread_pool.values[3] # [{"url":"https://api.github.com/repo ...

В основе реализации библиотеки FutureProof находится класс Queue, тот, что разрешает создавать безвредные для работы с большинством потоков очереди, гарантирующие, что несколько потоков не запишут в очередь значения единовременно поверх друг друга, и не считают единовременно одно и тоже значение.

В библиотеке разработана также обработка исключений — еслитаковое случилось во время исполнения потока, то thread_pool по бывшему сумеет воротить массив из полученных значений и вызовет исключение только в случае, если программист испробует напрямую обратиться к определенному элементу массива.

Имплементация групп потоков является попыткой приблизить вероятности Руби в работе с потоками к вероятностям Java и java.util.concurrent, откуда отчасти и черпалось воодушевление.

Применяя библиотеку FutureProof дозволено исполнять задачи, включающие работу с IO потоками, гораздо комфортнее и результативнее. Библиотека поддерживает версии Руби 1.9.3, 2.0, а так же Rubinius.

Потоки и вычислительные операции

Рассматривая удачный навык совершенствования быстродействия программы при помощи потоков, проведем два теста, в одном из которых мы 10000 раз по два раза вычислим факториал 1000 ступенчато, а в ином параллельно.

require 'benchmark'

factorial = Proc.new { |n|
  1.upto(n).inject(1) { |i, n| i * n }
}

Benchmark.bm do |x|
  x.report('sequential') do
    10_000.times do
      2.times do
        factorial.call 1000
      end
    end
  end

  x.report('thready') do
    10_000.times do
      threads = []
      2.times do
        threads << Thread.new do
          factorial.call 1000
        end
      end
      threads.each &:join
    end
  end
end

В результате мы получили довольно непредвиденный итог (применяя Ruby 2.0) — параллельное исполнение произвелось на секунду дольше:

               user     system      total        real
sequential 24.130000   1.510000  25.640000 (25.696196)
thready    24.600000   2.420000  27.020000 (26.877708)

Одна из причин — мы усложнили код планированием потоков, а вторая — Руби в один момент времени применял только одно ядро для исполнения этой программы. К сожалению, вероятности принудить Руби применять несколько ядер для одного ruby процесса на данный момент не предоставляется.

Дозвольте показать вам итоги исполнения этого же скрипта на jRuby 1.7.4:

               user     system      total        real
sequential 33.180000   0.690000  33.870000 (33.090000)
thready    37.820000   3.830000  41.650000 (24.333000)

Как дозволено подметить, итог стал отменнее. Т.к. застыл происходил на компьютере с двумя ядрами, и одно из ядер было использовано только на 75%, совершенствование не составило 200%. Но, следственно, на компьютере с огромным числом ядер мы могли бы делать еще огромнее параллельных потоков и еще огромнее усовершенствовать наш итог.

jRuby является альтернативной имплементацией Руби на JVM, превносящая в сам язык дюже крупные вероятности.

Выбирая число одновременных потоков нужно помнить — мы можем без потери продуктивности располагать уйма потоков, занимающихся IO операциями на одном ядре. Но мы будем немножко терять в продуктивности, если число потоков будет превосходить число ядер в случае с вычислительными операциями.

В случае с подлинной имплементацией Ruby (MRI) рекомендуется применять только один поток для вычислительных операций. Подлинного параллелизма при помощи потоков мы можем добиться только применяяjRuby и Rubinius.

Параллелизм на ярусе процессов

Как мы сейчас знаем, Ruby MRI для одного процесса ruby (в Unix системах) может применять источники только одного ядра в одним момент времени. Одним из вариантов, как мы можем обойти данный недочет, является применение форков процессов, скажем так:

read, write = IO.pipe
result      = 5

pid = fork do
  result = result   5
  Marshal.dump(result, write)
  exit 0
end

write.close
result = read.read
Process.wait(pid)
puts Marshal.load(result)

Форк процесса, на момент создания, копирует значение переменной result, равной 5, но основной процесс не увидит последующее метаморфоза переменной внутри форка, следственно нам было нужно наладить сообщение между форком и основным процессом при помощи IO.pipe.

Такой способ является действенным, но довольно массивным и неудобным. При помощи модуля DRb для дистрибутивного программирования, дозволено добиться больше увлекательных итогов.

Применяем модуль DRb для синхронизации процессов

Модуль DRb является частью стандартной Ruby библиотеки и отвечает за вероятности дистрибутивного программирования. В основе его идеи лежит вероятность дать доступ к одному Руби объекту любому компьютеру в сети. Итоги всех манипуляций с этим объектом, его внутреннее состояние, видно каждому подключенным компьютерам, и непрерывно синхронизировано. В целом, вероятности модуля дюже широки, и достойны отдельной статьи.

Мне пришла в голову идея применять кортежи Rinda::TupleSpace совместно с этой вероятностью DRb, Дабы сделать модуль Pthread, отвечающий за исполнение кода в отдельных процессах как на компьютере основной программы, так и на других подключенных машинах. Rinda::TupleSpace предлагает доступ к кортежам по имени и, как и объекты класса Queue, разрешают записывать и считывать кортежи только одному потоку либо процессу в одно время.

Таким образом, возникло решение, которое разрешает в Ruby MRI исполнять код на нескольких ядрах:

Pthread::Pthread.new queue: 'fact', code: %{
  1.upto(n).inject(1) { |i, n| i * n }
}, context: { n: 1000 }

Как вы могли подметить, код, тот, что нужно выполнить, подается как строка, т.к. в случае с процедурамиDRb передает иному процессу только ссылку на нее, и для ее исполнения пользуется источникам процесса, тот, что эту процедуру сотворил. Дабы избавиться от контекста основного процесса, я подаю код иным процессам как строку, а значения переменных строки в дополнительном словаре. Пример, как к исполнению кода подключать добавочные машины, дозволено обнаружить на домашней странице плана.

Библиотека Pthread поддерживает версии MRI 1.9.3 и 2.0.

Параллелизм в Ruby on Rails

Сервера для Ruby on Rails и библиотеки для исполнения фоновых задач дозволено разбить на две группы. Первая для обработки запросов пользователей либо исполнения фоновых задач использует форки — добавочные процессы. Таким образом, применяя MRI и эти сервера и библиотеки, мы можем обрабатывать параллельно сразу несколько запросов, и исполнять несколько задач единовременно.

Впрочем, у этого метода есть недочет. Форки процессов копируют память сделавшего их процесса, и таким образом сервер Unicorn с тремя «работниками» может забирать 1ГБ памяти, едва начав работу. Тоже самое касается библиотек для исполнения фоновых задач, скажем Resque.

Создатели сервера Puma для Ruby on Rails учли особенности jRuby и Rubinius, и выпустили сервер, ориентированный в первую очередь на эти две имплементации. В различии от того же UnicornPuma для одновременной обработки запросов использует потоки, которые требуют значительно поменьше памяти. Таким образом, Puma будет являться хорошей альтернативой при применении в связке совместно с jRuby либоRubinius. Следственно же тезису утроена библиотека Sidekiq.

Завершение

Потоки являются дюже мощным инструментом, дозволяющим делать несколько пророческой одноврем

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB