portaldacalheta.pt
  • Основен
  • Възходът На Дистанционното
  • Хора И Екипи
  • Инструменти И Уроци
  • Технология
Back-End

Рубин паралелност и паралелизъм: Практически урок



Нека започнем, като изчистим твърде често срещаната точка на объркване сред Разработчици на Ruby ; а именно: Съвпадението и паралелизмът са не едно и също нещо (т.е. едновременно! = паралелно).

как да възпроизвеждате m3u8 файлове на android

По-специално, Руби съвпадение е когато две задачи могат да стартират, стартират и завършат припокриващи се периоди от време. Това обаче не означава непременно, че и двамата ще работят едновременно (напр. Множество нишки на едноядрена машина). За разлика, паралелизъм е когато буквално се изпълняват две задачи по същото време (напр. множество нишки на многоядрен процесор).



Ключовият момент тук е, че едновременните нишки и / или процеси ще не е задължително да работи паралелно.



Този урок предоставя практическо (а не теоретично) третиране на различните техники и подходи, които са на разположение за едновременност и паралелизъм в Ruby.



За повече реални примери за Ruby вижте нашата статия за Руби интерпретатори и времетраене .

Нашият тестов случай

За прост тестов случай ще създам Mailer клас и добавете функция на Фибоначи (а не метода sleep()), за да направите всяка заявка по-интензивна за процесора, както следва:



class Mailer def self.deliver(&block) mail = MailBuilder.new(&block).mail mail.send_mail end Mail = Struct.new(:from, :to, :subject, :body) do def send_mail fib(30) puts 'Email from: #{from}' puts 'Email to : #{to}' puts 'Subject : #{subject}' puts 'Body : #{body}' end def fib(n) n <2 ? n : fib(n-1) + fib(n-2) end end class MailBuilder def initialize(&block) @mail = Mail.new instance_eval(&block) end attr_reader :mail %w(from to subject body).each do |m| define_method(m) do |val| @mail.send('#{m}=', val) end end end end

След това можем да извикаме това Mailer клас, както следва за изпращане на поща:

Mailer.deliver do from ' [email protected] ' to ' [email protected] ' subject 'Threading and Forking' body 'Some content' end

(Забележка: Изходният код за този тест е наличен тук на github.)



За да установим базова линия за целите на сравнението, нека започнем, като направим прост бенчмарк, извикващ пощенския код 100 пъти:

puts Benchmark.measure{ 100.times do |i| Mailer.deliver do from 'eki_#{i}@eqbalq.com' to 'jill_#{i}@example.com' subject 'Threading and Forking (#{i})' body 'Some content' end end }

Това даде следните резултати за четириядрен процесор с MRI Ruby 2.0.0p353:



15.250000 0.020000 15.270000 ( 15.304447)

Множество процеси срещу многопоточност

Няма отговор „универсален за всички“, когато става въпрос за решение дали да се използват множество процеси или да се използва многопоточност на вашето приложение Ruby. Таблицата по-долу обобщава някои от ключовите фактори, които трябва да се вземат предвид.

Процеси Конци
Използва повече памет Използва по-малко памет
Ако родителят умре преди децата да са излезли, децата могат да станат зомби процеси Всички нишки умират, когато процесът умре (няма шанс за зомбита)
По-скъпо за раздвоени процеси за превключване на контекста, тъй като ОС трябва да запазва и презарежда всичко Нишките имат значително по-малко режийни разходи, тъй като споделят адресно пространство и памет
Раздвоените процеси получават ново пространство за виртуална памет (изолиране на процеса) Нишките споделят една и съща памет, така че трябва да контролирате и да се справяте с едновременни проблеми с паметта
Изисква комуникация между процесите Може да „комуникира“ чрез опашки и споделена памет
По-бавно за създаване и унищожаване По-бързо за създаване и унищожаване
По-лесно се кодира и отстранява грешки Може да бъде значително по-сложен за кодиране и отстраняване на грешки

Примери за решения на Ruby, които използват множество процеси:



  • Благосъстояние : Подкрепена от Redis Ruby библиотека за създаване на фонови задачи, поставянето им на множество опашки и обработката им по-късно.
  • еднорог : HTTP сървър за Rack приложения, предназначен да обслужва само бързи клиенти на връзки с ниска латентност и висока честотна лента и да се възползва от функциите в Unix / Unix-подобни ядра.

Примери за решения на Ruby, които използват многопоточност:

  • Sidekiq : Пълнофункционална рамка за фонова обработка на Ruby. Целта му е да се интегрира лесно с всяко съвременно приложение на Rails и много по-висока производителност от други съществуващи решения.
  • Пума : Уеб сървър на Ruby, създаден за едновременност.
  • Тънка : Много бърз и прост уеб сървър на Ruby.

Множество процеси

Преди да разгледаме опциите за многопоточност на Ruby, нека разгледаме по-лесния път за хвърляне на хайвера на множество процеси.



В Ruby, fork() системното повикване се използва за създаване на „копие“ на текущия процес. Този нов процес е насрочен на ниво операционна система, така че може да работи едновременно с оригиналния процес, точно както всеки друг независим процес. ( Забележка: fork() е POSIX системно обаждане и следователно не е налично, ако използвате Ruby на платформа на Windows.)

Добре, нека да стартираме нашия тестов случай, но този път използвайки fork() да се използват множество процеси:

puts Benchmark.measure{ 100.times do |i| fork do Mailer.deliver do from 'eki_#{i}@eqbalq.com' to 'jill_#{i}@example.com' subject 'Threading and Forking (#{i})' body 'Some content' end end end Process.waitall }

(Process.waitall изчаква всичко дъщерни процеси, за да излезе и връща масив от състояния на процеса.)

Този код сега дава следните резултати (отново на четириядрен процесор с MRI Ruby 2.0.0p353):

0.000000 0.030000 27.000000 ( 3.788106)

Не твърде изтъркано! Направихме пощенската програма ~ 5 пъти по-бърза, като просто модифицирахме няколко реда код (т.е. използвайки fork()).

$scope не е дефиниран

Не се вълнувайте прекалено много. Въпреки че може да е изкушаващо да се използва форкинг, тъй като това е лесно решение за съвпадение на Ruby, той има основен недостатък, който е количеството памет, което ще консумира. Разклонението е малко скъпо, особено ако a Копиране при запис (CoW) не се използва от интерпретатора Ruby, който използвате. Ако приложението ви използва 20MB памет, например, раздвояването му 100 пъти може потенциално да отнеме до 2GB памет!

Също така, въпреки че многопоточността има и свои собствени сложности, има редица сложности, които трябва да се имат предвид при използване на fork(), като например споделени файлови дескриптори и семафори (между родителски и дъщерни раздвоени процеси), необходимостта от комуникация чрез тръби и т.н.

Руби Multithreading

Добре, така че нека сега се опитаме да направим същата програма по-бърза, като използваме техниките за многопоточност Ruby.

Множество нишки в рамките на един процес имат значително по-малко режийни разходи, отколкото съответния брой процеси, тъй като те споделят адресно пространство и памет.

Имайки това предвид, нека преразгледаме нашия тестов случай, но този път използвайки Ruby’s Thread клас:

threads = [] puts Benchmark.measure{ 100.times do |i| threads << Thread.new do Mailer.deliver do from 'eki_#{i}@eqbalq.com' to 'jill_#{i}@example.com' subject 'Threading and Forking (#{i})' body 'Some content' end end end threads.map(&:join) }

Този код сега дава следните резултати (отново на четириядрен процесор с MRI Ruby 2.0.0p353):

13.710000 0.040000 13.750000 ( 13.740204)

Ужасно. Това със сигурност не е много впечатляващо! Е, какво става? Защо това води до почти същите резултати, които получихме, когато стартирахме кода синхронно?

Отговорът, който е проклетникът на съществуването на много програмисти на Ruby, е Заключване на глобален преводач (GIL) . Благодарение на GIL, CRuby (изпълнението на ЯМР) всъщност не поддържа резби.

The Заключване на глобален преводач е механизъм, използван в интерпретаторите на компютърен език за синхронизиране на изпълнението на нишки, така че само една нишка да може да се изпълни наведнъж. Преводач, който използва GIL will винаги позволяват точно една нишка и една нишка само за изпълнение в даден момент , дори ако се изпълнява на многоядрен процесор. Ruby MRI и CPython са два от най-често срещаните примери за популярни интерпретатори, които имат GIL.

И така, обратно към нашия проблем, как можем да използваме многопоточност в Ruby, за да подобрим производителността в светлината на GIL?

Е, в ЯМР (CRuby), жалкият отговор е, че по принцип сте закъсали и много малко, което многопоточността може да направи за вас.

Съвпадението на Ruby без паралелизъм все пак може да бъде много полезно за задачи, които са тежки с IO (например задачи, които често трябва да чакат в мрежата). Така че нишки мога все още да бъде полезен в ЯМР, за IO-тежки задачи. Има причина в края на краищата нишките да са измислени и използвани дори преди многоядрените сървъри да са били често срещани.

Но въпреки това, ако имате възможност да използвате версия, различна от CRuby, можете да използвате алтернативно изпълнение на Ruby като JRuby или Рубиний , тъй като нямат GIL и поддържат истински паралелни нишки на Ruby.

резбовани с JRuby

За да докажем същността, ето резултатите, които получаваме, когато стартираме същата версия на кода с резба, както преди, но този път го стартирайте на JRuby (вместо на CRuby):

43.240000 0.140000 43.380000 ( 5.655000)

Сега говорим!

Но…

какви са компонентите в ъгловата

Темите не са безплатни

Подобрената производителност с множество нишки може да накара човек да повярва, че можем просто да продължим да добавяме повече нишки - основно безкрайно - за да продължим да правим кода ни да работи по-бързо и по-бързо. Това наистина би било хубаво, ако беше вярно, но реалността е, че нишките не са безплатни и така рано или късно ще останете без ресурси.

Да кажем, например, че искаме да стартираме нашия примерен пощенски код не 100 пъти, а 10 000 пъти. Нека да видим какво ще стане:

threads = [] puts Benchmark.measure{ 10_000.times do |i| threads << Thread.new do Mailer.deliver do from 'eki_#{i}@eqbalq.com' to 'jill_#{i}@example.com' subject 'Threading and Forking (#{i})' body 'Some content' end end end threads.map(&:join) }

Бум! Получих грешка с моята OS X 10.8, след като породих около 2000 нишки:

can't create Thread: Resource temporarily unavailable (ThreadError)

Както се очакваше, рано или късно започваме да бием или изчерпваме изцяло ресурсите. Така че мащабируемостта на този подход е очевидно ограничена.

Обединяване на нишки

За щастие има по-добър начин; а именно обединяване на нишки.

Пул от нишки е група от предварително създадени, повторно използваеми нишки, които са на разположение за извършване на работа при необходимост. Пуловете от нишки са особено полезни, когато има голям брой кратки задачи, които трябва да бъдат изпълнени, а не малък брой по-дълги задачи. Това предотвратява налагането на режийни при създаването на нишка голям брой пъти.

Ключовият конфигурационен параметър за пула от нишки обикновено е броят на нишките в пула. Тези нишки могат да бъдат инстанцирани наведнъж (т.е. при създаване на пула) или лениво (т.е. според необходимостта, докато не бъде създаден максималният брой нишки в пула).

Когато на пула е дадена задача за изпълнение, той я присвоява на една от текущите неактивни нишки. Ако нито една нишка не е на празен ход (а максималният брой нишки вече е създаден), тя изчаква нишка да завърши работата си и да стане неактивна и след това присвоява задачата на тази нишка.

Обединяване на нишки

Така че, връщайки се към нашия пример, ще започнем с Queue (тъй като е нишка безопасна тип данни) и използвайте проста реализация на пула от нишки:

изисква „./lib/mailer“ изисква „бенчмарк“ изисква „конец“

POOL_SIZE = 10 jobs = Queue.new 10_0000.times jobs.push i workers = (POOL_SIZE).times.map do Thread.new do begin while x = jobs.pop(true) Mailer.deliver do from 'eki_#{x}@eqbalq.com' to 'jill_#{x}@example.com' subject 'Threading and Forking (#{x})' body 'Some content' end end rescue ThreadError end end end workers.map(&:join)

В горния код започнахме със създаването на jobs опашка за заданията, които трябва да бъдат изпълнени. Използвахме Queue за тази цел, тъй като е безопасно за нишки (така че ако няколко нишки имат достъп до него едновременно, той ще поддържа последователност), което избягва необходимостта от по-сложно изпълнение, изискващо използването на мутекс .

След това избутахме идентификаторите на пощенските изпращачи в опашката за работа и създадохме нашия пул от 10 работни нишки.

Във всяка работна нишка изскачаме елементи от опашката за работни места.

По този начин жизненият цикъл на работната нишка е непрекъснато да чака задачите да бъдат поставени в опашката на заданието и да ги изпълнява.

Така че добрата новина е, че това работи и се мащабира без никакви проблеми. За съжаление обаче това е доста сложно дори за нашия прост урок.

Целулоид

Благодаря на Рубин скъпоценен камък екосистема, голяма част от сложността на многопоточността е спретнато капсулирана в множество лесни за използване Ruby Gems, които са готови.

Чудесен пример е Celluloid, един от любимите ми скъпоценни камъни. Celluloid framework е прост и изчистен начин за внедряване на базирани на актьори едновременни системи в Ruby. Целулоид дава възможност на хората да изграждат едновременни програми от едновременни обекти също толкова лесно, колкото и да изграждат последователни програми от последователни обекти.

В контекста на нашата дискусия в тази публикация, аз специално се фокусирам върху функцията Пулове, но направете си услуга и я разгледайте по-подробно. Използвайки Celluloid, ще можете да изграждате многонишкови Ruby програми, без да се притеснявате за неприятни проблеми като мъртви блокировки и ще ви бъде тривиално да използвате други по-сложни функции като Futures и Promises.

Ето колко проста многонишкова версия на нашата пощенска програма използва Celluloid:

require './lib/mailer' require 'benchmark' require 'celluloid' class MailWorker include Celluloid def send_email(id) Mailer.deliver do from 'eki_#{id}@eqbalq.com' to 'jill_#{id}@example.com' subject 'Threading and Forking (#{id})' body 'Some content' end end end mailer_pool = MailWorker.pool(size: 10) 10_000.times do |i| mailer_pool.async.send_email(i) end

Чист, лесен, мащабируем и здрав. Какво повече можете да поискате?

Задания на заден план

Разбира се, друга потенциално жизнеспособна алтернатива, в зависимост от вашите оперативни изисквания и ограничения, би била да използвате фонови работни места . Съществуват редица Ruby Gems, които поддържат фонова обработка (т.е. запазване на задания в опашка и обработка по-късно, без да се блокира текущата нишка). Забележителните примери включват Sidekiq , Благосъстояние , Забавена работа , и Beanstalkd .

За тази публикация ще използвам Sidekiq и Редис (кеш и съхранение на ключ-стойност с отворен код).

рамка на обектен модел на страница в selenium webdriver

Първо, нека инсталираме Redis и го стартираме локално:

brew install redis redis-server /usr/local/etc/redis.conf

С нашия локален екземпляр на Redis, нека да разгледаме версия на нашата примерна програма за поща (mail_worker.rb), използвайки Sidekiq:

require_relative '../lib/mailer' require 'sidekiq' class MailWorker include Sidekiq::Worker def perform(id) Mailer.deliver do from 'eki_#{id}@eqbalq.com' to 'jill_#{id}@example.com' subject 'Threading and Forking (#{id})' body 'Some content' end end end

Можем да задействаме Sidekiq с mail_worker.rb файл:

sidekiq -r ./mail_worker.rb

И след това от IRB :

⇒ irb >> require_relative 'mail_worker' => true >> 100.times 2014-12-20T02:42:30Z 46549 TID-ouh10w8gw INFO: Sidekiq client with redis options {} => 100

Страхотно просто. И може лесно да се мащабира само чрез промяна на броя на работниците.

Друг вариант е да се използва Sucker Punch , една от любимите ми асинхронни библиотеки за обработка на RoR. Изпълнението, използващо Sucker Punch, ще бъде много подобно. Просто ще трябва да включим SuckerPunch::Job вместо Sidekiq::Worker и MailWorker.new.async.perform() по-скоро MailWorker.perform_async().

Заключение

Високата паралелност не е постижима само в Руби , но е и по-проста, отколкото си мислите.

Един жизнеспособен подход е просто да се форкира работещ процес, за да се умножи неговата процесорна мощ. Друга техника е да се възползвате от многопоточност. Въпреки че нишките са по-леки от процесите и изискват по-малко режийни разходи, пак можете да останете без ресурси, ако стартирате твърде много нишки едновременно. В даден момент може да ви се наложи да използвате пул от нишки. За щастие, много от сложностите на многопоточност са улеснени чрез използване на някой от редица налични скъпоценни камъни, като Celluloid и неговия модел на актьора.

Друг начин за справяне с отнемащи време процеси е използването на фонова обработка. Има много библиотеки и услуги, които ви позволяват да внедрите задания на заден план във вашите приложения. Някои популярни инструменти включват рамки за задачи, поддържани от база данни, и опашки за съобщения.

Разклоняването, резбоването и обработката на фона са жизнеспособни алтернативи. Решението кой да се използва зависи от естеството на вашето приложение, вашата оперативна среда и изисквания. Надяваме се, че този урок е предоставил полезно въведение в наличните опции.

WordPress Непрекъснато внедряване и контрол на версиите с Bitbucket

Уеб Интерфейс

WordPress Непрекъснато внедряване и контрол на версиите с Bitbucket
Проектирайте бъдещето: Инструментите и продуктите, които ни очакват

Проектирайте бъдещето: Инструментите и продуктите, които ни очакват

Ux Дизайн

Популярни Публикации
Урок за ъглов 5: Ръководство стъпка по стъпка към първото ви приложение за ъглови 5
Урок за ъглов 5: Ръководство стъпка по стъпка към първото ви приложение за ъглови 5
Лов на Java изтичане на памет
Лов на Java изтичане на памет
SMB акаунт мениджър
SMB акаунт мениджър
Изградете ултрамодерни уеб приложения с ъглови материали
Изградете ултрамодерни уеб приложения с ъглови материали
Влияние върху влиятелните лица: Фирми-анализатори
Влияние върху влиятелните лица: Фирми-анализатори
 
Ръководство за стил на Sass: Урок за Sass за това как да напишем по-добър CSS код
Ръководство за стил на Sass: Урок за Sass за това как да напишем по-добър CSS код
Принцип на единната отговорност: Рецепта за велик кодекс
Принцип на единната отговорност: Рецепта за велик кодекс
Plant Power: Поглед върху отвъд месото и неговите конкуренти
Plant Power: Поглед върху отвъд месото и неговите конкуренти
Директор на скрининг на таланти
Директор на скрининг на таланти
Как да провеждаме тестове за използваемост в шест стъпки
Как да провеждаме тестове за използваемост в шест стъпки
Популярни Публикации
  • ефектите на цвета върху хората
  • защо един архитект може да избере прост, повтарящ се ритъм за своята сграда?
  • за какво се използва docx
  • компилиране на c++ файл
  • доста често трябва да използвате това изявление, за да направите група от класове достъпна за програма.
Категории
  • Възходът На Дистанционното
  • Хора И Екипи
  • Инструменти И Уроци
  • Технология
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt