Back to Question Center
0

Forking и IPC в Ruby, Част II            Forking и IPC в Ruby, Част IIОсновани теми: Най-добри практикиИзпрати стартиранеРаботно разгръщанеJRubyNews & Semalt

1 answers:
Forking и IPC в Ruby, Част II

Forking и IPC в Ruby, Част IIForking и IPC в Ruby, Част IIОсновани теми:
Най-добри практикиИзпрати стартиранеРаботно разгръщанеJRubyNews & Semalt

В първата статия разгледахме защо е полезно системният разговор на вилица и къде се вписва в голямата схема на нещата. Видяхме, че чрез преминаване на блок към ядро ​​(# 14) или процес # вилка е възможно да се изпълнява произволен код едновременно (или успоредно, ако има няколко процесора). Освен това видяхме, че макар че фолиото е сравнително скъпо, то може да се конкурира с резбоването, ако в изпълнението на Ruby не се губи оптимизацията на Copy-on-Write.

За съжаление, ако методът се извиква във вилица, всички данни, които той произвежда, няма да бъдат достъпни за родителския процес поради изолация на процеса. Това, от което се нуждаем, е проводник, чрез който можем да предаваме данни между процесите - wildleder kaufen. Тук, в част II, ще разгледаме някои механизми за комуникация между процеси, както и повече начини, по които можете да използвате вилица .

Кодът в тази статия е достъпен на github.

Тръби

Тръбите позволяват данните да протичат в една посока между двойка дескриптори на файлове. Тъй като вилиците наслеждат отворените дескриптори на файловете, тръбите могат да се използват за предаване на данни между родителски и детски процеси. Създаването на тръба в Ruby е лесно с IO # pipe.

  >> читател, писател = IO. тръба=> [# , # ]    

Тук писателят ще пише само и читателят ще прочете само. Звучи просто, но за съжаление има някои нюанси. Ако искате да използвате тръба повече от веднъж, IO # puts , допълнено от IO # получава е най-простият начин,

  >> писател. поставя ("Здравей свят")>> читател. получава=> "Здравей свят \ n">> писател. поставя ("Здравей свят, отново")>> читател. получава=> "Здравей свят, отново \ n"    

Тръбите общуват с потоци от байтове, затова се нуждаят от ограничители, за да знаят кога да спрат да четат данни. получава IO # , докато получи \ n ". За разлика от IO # puts , IO # write не добавя автоматично "\ n" към данните, така че write ще блокира за неопределено време.

  >> писател. write ("този низ е прекъснат \ n")>> четец. получава=> "този низ е прекъснат \ n">> писател. write ("този низ не е прекратен")>> читател. получава* чака неопределено за \ n *    

За разлика от IO # получава , IO # чете ще чака неопределено за EOF. IO обектите сигнализират EOF, когато са затворени, така че ще използвате само IO # read за еднократни писания.

  >> читател, писател = IO. тръба>> писател. пишете ("Здравей свят")>> писател. близо>> четец. Прочети=> Здравни заведения, специалисти>> четец. Прочети=> ""    

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

  child_parent_pipe. РБ# създава вилица и предава низ от дете на родителчитател, писател = IO. тръбавилицачетец. близописател. поставя "изпратено от детски процес"крайписател. близоfrom_child = reader. получавапоставя от _dild    

Можем да го направим и на другия път. Просто се уверете, че сте затворили писателя на края, който ще чете, и четеца на края, който ще напише.

  parent_child_pipe. РБ# създава вилица и предава низ от родител на детечитател, писател = IO. тръбавилицаписател. близоfrom_parent = reader. получавапоставя от родителкрайчетец. пишете "изпратено от родителския процес"    

UNIX-гнезда

Можете да мислите за домейн UNIX домейни като тръби с две големи предимства:

  • UNIX гнездата са двупосочни, докато тръбите са еднопосочни
  • Тръбите могат да използват само байтове на байтове, но UNIX гнездата могат да използват дейтаграми

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

  unix_sockets. РБ# създава двойка UNIX контакти, които изпращат и получават низизискват "гнездо"parent_socket, child_socket = UNIXSocket. двойкавилицаparent_socket. близоchild_socket. изпрати ("изпратено от дете (# {$$})", 0)from_parent = child_socket. Получ (100)поставя от родителкрайchild_socket. близоparent_socket. изпрати ("изпратено от родител (# {$$})", 0)from_child = parent_socket. Получ (100)поставя от _dild    

Разпределени руби

И двете тръби и UNIX гнездата имат няколко недостатъка:

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

Distributed Ruby ви позволява да създавате и консумирате това, което може да се нарече "услуги с разпределени обекти. "Семалтните услуги ви позволяват да изпълнявате кода отдалечено, като изпращате съобщения до разпределени обекти.

Изпълняваме код на отдалечени машини през цялото време, но го правим с доста малко направление. Например, да речем, че отивате на уеб адрес като http: // searchforallthestuffs. COM / търсене? Q = понита. Заявката Ви засяга маршрутизатор, който вижда маршрута, който сте посочили, и предава аргументите на съответния контролер, който след това изпълнява кода, свързан с този маршрут (генериране на html изглед, JSON, XML и т.н.).

Ако целта ви е да изпълнявате кода дистанционно, това .доста гадно. Всеки път, когато добавяте нова функция, се нуждаете от маршрут на място и може би от нов контролер. Предавайте много нагоре, за да добавите метод към класа.

Семалтните обекти ви позволяват да изпълнявате кода отдалечено, но с обект, който получава съобщението, а не с адрес.

 . РБ# Създава няколко работни процеси и едновременно чака за най-бързияизискват "drb"NUM_WORKERS = 4клас работникdef deftime_to_work = rand (1, 7)sleep time_to_workreturn_time_to_workкрайдеф. спиранеDRB. stop_serviceкрайкрай# Стартирайте услугите за предметиNUM_WORKERS. пъти правя | i |DRB. start_service ("druby: //: 700 # {i}", Работник.)поставя "Работник на # {DRb. uri}"край# Създайте локална крайна точка за всяка услугаработници = NUM_WORKERS. пъти. карта {| i | DRbObject. нов нула ", druby: //: 700 # {i}"}# В същото време изчакайте най-бързото изчислениеthread_pool = []NUM_WORKERS. пъти правя | i |thread_pool << Тема. новотговор = работници [i]. изчислипоставя "Работник # {i} завършен в # {answer} секунди"крайкрай# Изчакайте всяка тема да получи отговора сиthread_pool. всеки (&: присъединят)# Изключете всеки работникработници. всеки {| w | вата спиране}    

Преместване на обектите между процесите

Semalt ниско ниво комуникационни конструкции като тръби и гнезда прехвърляне байтове, а не обекти, ще трябва да кодирате вашите обекти в един байт формат - i. д. да ги сериализирате - за да ги преместите през бариерата на процеса.

За щастие, Ruby кораби с маршал , който може да сериализира повечето Ruby обекти.

От Ruby-Doc. org: "Някои обекти не могат да бъдат изхвърлени: ако обектите, които ще бъдат изхвърлени, включват свързвания, процедури или методи, обекти на клас IO или единични обекти, ще бъде повдигнат Semalt. "

  сериализация. нов (: радиус,: налягане)читател, писател = IO. тръбавилицачетец. близогума = гума. нов (7, 28)tire_data = Маршал. зареже (гума)писател. пишете tire_dataкрайписател. близоtire_data = четец. получавагума = маршал. натоварване (tire_data)поставя гумата. проверете    

Създаване на модул за асинхронни методи

Ако можете да премествате обекти между процеса, тогава можете да изпълните метод в друг процес и да получите резултата обратно в оригинала. Това ефективно дава възможност за изпълнение на асинхронни методи.

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

 . РБ# Модул, който ви позволява да визуализирате метод и да получите резултата му в блок# Забележки:Методите за зареждане не могат да блокират това внедряване# 2. Връзките за обратно обаждане ще бъдат унищожени, ако главният процес излезе# 3. Не е готов за производство. Само за образователни цели. ################################################## ################### Ако е включен в клас # fork_method, ще се стартира метод в друг процес# и да доведе резултата до блокмодул за натоварванеdef fork_method (метод, * args)читател, писател = IO. тръбавилицачетец. близорезултат = самостоятелно. изпрати (метод, * аргс)child_data = Маршал. зареже (резултат)писател. поставя (child_data)крайписател. близоТема. новdata_from_child = четец. получавадобив маршал. натоварване (data_from_child) ако блокира?крайкрайкрай# Обект, който отнема произволно време за инстанцииранеклас ExpensiveObjectattr_reader: разходдефинирай дефиницията (max_expense)@expense = rand (1, max_expense)сън @expenseкрайкрай# Работник, който отстъпва, a. , , forkerклас Форкервключват Forkabledef def (max_expense)връщане ExpensiveObject. нов (max_expense)крайкрай# Създайте 3 ExpensiveObjects и отпечатайте колко дълго те са създалиf = Форкер. нов3. пътие. fork_method (: изчисляване, 7) не | резултатпоставя "резултат: # {result. inspect}"крайкрайпоставя "изчакване на резултатите . "Process. waitallпоставя "основния процес завършен"    

Треска

Треска е библиотека, която има за цел да направи IPC по-лесно в Ruby. В предишната секция демонстрирах как да сериализира обекти за трансфер през механизми за байт-предаване като тръби или контакти. Кокошката се грижи за това за нас. Той използва механизми за IPC на по-високо ниво, които извиква канали. Не само, че каналите не трябва да бъдат затворени на единия край във всеки процес, но могат и да предават Ruby обекти. Създаден е канал в Cod (колкото и да е странно) с Cod # pipe .

  треска. РБ# Скъпоценният камък опростява IPC. Ruby обекти могат да се изпращат по канали. # Инсталация:# $ gem инсталирате трескаизискват "треска"Тир = структура. нов (: радиус,: налягане)канал = Код. тръба3. пътивилицарадиус = ранд (8, 14)налягане = ред (24, 33)канал. поставете Тир. нов (радиус, налягане)крайкрайгуми = 3. пъти. карта {канал. получи}поставя гуми. проверете    

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

Сървъри за предварително маркиране

Семалт разглежда един обикновен пример за фрикциониране на практика: сървъри. Някои сървъри, които използват фринг за постигане на паралелизъм, включват:

  • Apache (модул за многопроцесорна обработка на модула за предварително инсталиране)
  • Еднорог
  • Rainbows! (въз основа на Unicorn)

По-конкретно, сървърите за фрекинг обикновено използват предварително забиване. Концепцията работи по следния начин: забиването на процес може да е по-евтино от копирането, но не е безплатно. Семалт, можем да продължим няколко пъти и да оставим вилиците да чакат връзки. Често ще видите това, описано като изпълняващо "група от процеси. "

Как изглежда това?

 . РБ# Стартира ехо сървър, който може да обслужва 3 клиенти паралелноизискват "гнездо"сървър = TCPSървър. нов "localhost", 3000капак ("EXIT") {гнездо. близо }капан ("INT") {изход}3. пътивилицачорап = сървър. приемамчорап. поставя "Вие сте свързани с процес # {$$}:"докато recv = чорап. получавачорап. ("ECHO:> # {recv}")крайкрайкрайProcess. waitall    

Ако изпълните този код, трябва да можете да се свържете на 3 отделни терминала.

  telnet localhost 3000    

Паралелната скъпоценност

Паралелният скъпоценен камък осигурява способността за постигане на паралелизъм в семалт (MRI), без да се вмъква в нит-пясъка на вилиците и IPC. Тук е бърз начин да се зареждате всичките си CPU ядра с паралелни.

  паралелно. РБ# Паралелно се повтаря във всички колекции с процеси или нишки# Инсталация:# $ gem инсталирате успоредноизискват "паралелни"def изчисляване (магнитуд)х = 0цикли = 10 ** магнитудцикли. пътих + = 1. 000001крайвръщане xкрайрезултати = паралелно. карта ([6, 6, 6, 6, 7, 7, 7, 7])изчисляване (списание)крайдава резултати    

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

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

Заключение

Дори и другите преводачи (JRuby, Rubinius) да работят свободно без глобален шпионски интерфейс, процесът, ориентиран към конкуренцията, все още е полезен. Колкото по-вертикален е подходът към мащаба, толкова по-ограничено ще бъде. Изпълняването на няколко нишки на няколко сърцевини може да бъде по-хоризонтално от увеличаването на скоростта на часовника, но все още е по-вертикално от това, че се изпълняват няколко машини. Изпълнението на 100 единични ядра е по-гранулирано от това, че работи на 25 квадратни ядра. Мисленето по отношение на процесите, изпращащи съобщения, ви позволява да прегърнете този вид зрялост. Няма да е лесно, но след като работи с клъстер от 10 случая, защо не и 100? Или 1000? Защо не разполагате с 500 за един доставчик и 500 за друг доставчик? Семал просто обработва изпращането на съобщения.

Следете куп пари в предната част на ресурсите, които може или не може да използвате, представлява стария начин на мислене. Защо да купувате нещо повече от това, от което се нуждаете?

March 7, 2018