Чому супроводи повернулися? [зачинено]


19

Більшість основ роботи над корутинами відбулися в 60-х / 70-х роках, а потім припинилися на користь альтернатив (наприклад, теми)

Чи є яка-небудь субстанція до відновленого інтересу до процедур, що виникають у пітоні та інших мовах?



9
Я не впевнений, що вони коли-небудь пішли.
Blrfl

Відповіді:


26

Розслідування ніколи не виходило, тим часом вони були просто затьмарені іншими речами. Нещодавно підвищений інтерес до асинхронного програмування і, отже, сутопрограм, значною мірою пояснюється трьома факторами: посиленим прийняттям функціональних методів програмування, набором інструментів з поганою підтримкою справжнього паралелізму (JavaScript! Python!), І найголовніше: різними компромісами між потоками та підпрограмми. Для деяких випадків використання супроводи об'єктивно кращі.

Однією з найбільших парадигм програмування 80-х, 90-х років і сьогодні є OOP. Якщо ми подивимось на історію ООП та конкретно на розвиток мови Simula, то побачимо, що заняття еволюціонували з кореневих процедур. Симулятор призначений для моделювання систем з дискретними подіями. Кожен елемент системи був окремим процесом, який би виконувався у відповідь на події протягом тривалості одного кроку моделювання, а потім поступався, щоб інші процеси виконували свою роботу. Під час розробки Simula 67 була представлена ​​концепція класу. Тепер стійкий стан підпрограми зберігається в елементах об'єкта, а події ініціюються викликом методу. Більш детально розглянемо читання статті Розвиток мов SIMULA від Nygaard & Dahl.

Тож у кумедному повороті ми весь час використовували супроводи, ми просто називали їх об’єктами та програмованими подіями програмами.

Щодо паралелізму, існують два види мов: мова, яка має належну модель пам'яті, та мова, яка її не має. Модель пам'яті обговорює такі речі, як "Якщо я записую в змінну і після цього читаю з цієї змінної в іншому потоці, чи бачу я старе значення чи нове значення чи, можливо, недійсне значення? Що означає "до" і "після"? Які операції гарантовано є атомними? "

Створити хорошу модель пам’яті складно, тому цього зусилля просто ніколи не робилося для більшості цих не визначених, визначених реалізацією динамічних мов з відкритим кодом: Perl, JavaScript, Python, Ruby, PHP. Звичайно, всі ці мови розвивалися далеко за рамки «сценарію», для якого вони були спочатку побудовані. Що ж, деякі з цих мов мають якийсь зразок моделі пам’яті, але їх недостатньо. Натомість у нас є хаки:

  • Perl може бути складено з підтримкою нитки, але кожен потік містить окремий клон повного стану інтерпретатора, що робить нитки непомірно дорогими. В якості єдиної вигоди, такий підхід на загальне використання не дозволяє уникнути перегонів даних і змушує програмістів спілкуватися лише через черги / сигнали / IPC. Perl не має важливої ​​історії для обробки асинхронізації.

  • JavaScript завжди мав багату підтримку функціонального програмування, тому програмісти вручну кодували продовження / зворотній зв'язок у своїх програмах там, де вони потребували асинхронних операцій. Наприклад, із запитами Ajax або затримками анімації. Оскільки в Інтернеті по суті є асинхронність, існує багато асинхронного коду JavaScript, а керування всіма цими зворотними зворотами надзвичайно болісно. Тому ми бачимо багато зусиль, щоб краще організувати ці зворотні виклики (Обіцяння) або повністю усунути їх.

  • У Python є ця нещасна особливість під назвою Global Interpreter Lock. В основному модель пам'яті Python - це "Всі ефекти з'являються послідовно, оскільки немає паралелізму. Тільки один потік одночасно запускає код Python. ”Отже, хоча Python має нитки, вони є такими ж потужними, як і підпрограми. [1] Python може кодувати безліч процедур за допомогою функцій генератора за допомогою yield. При правильному використанні, це одне лише дозволяє уникнути більшості пекла зворотного виклику, відомого з JavaScript. Більш свіжа система асинхронізації / очікування від Python 3.5 робить асинхронні ідіоми більш зручними в Python та інтегрує цикл подій.

    [1]: Технічно ці обмеження поширюються лише на CPython, реалізацію посилання Python. Інші реалізації, такі як Jython, пропонують реальні потоки, які можна виконувати паралельно, але вони повинні пройти велику довжину, щоб реалізувати еквівалентну поведінку. По суті: кожна змінна або об'єкт є мінливою змінною, так що всі зміни є атомними і їх відразу бачать у всіх потоках. Звичайно, використання змінних змінних набагато дорожче, ніж використання звичайних змінних.

  • Я не знаю достатньо про Ruby та PHP, щоб правильно їх смажити.

Підводячи підсумок: деякі з цих мов мають фундаментальні дизайнерські рішення, які роблять багатопотоковість небажаною або неможливою, що призводить до посилення уваги до таких альтернатив, як супроводи та способи зробити програмування асинхронізації більш зручним.

Наостанок поговоримо про відмінності між супрограмами та потоками:

Нитки в основному схожі на процеси, за винятком того, що декілька потоків всередині процесу поділяють простір пам'яті. Це означає, що нитки аж ніяк не є «легкою вагою» з точки зору пам’яті. Нитки попередньо плануються операційною системою. Це означає, що перемикачі завдань мають високі накладні витрати і можуть виникати в незручні моменти. Цей наклад має два компоненти: вартість призупинення стану потоку та вартість перемикання між користувацьким режимом (для потоку) та режимом ядра (для планувальника).

Якщо процес планує власні потоки безпосередньо та спільно, контекстний перехід у режим ядра є непотрібним, а завдання перемикання порівняно дорогі для непрямого виклику функції, як у: досить дешево. Залежно від різних деталей, ці легкі нитки можна назвати зеленими нитками, волокнами або супроводами. Помітні користувачі зелених ниток / волокон були ранньою реалізацією Java, а останнім часом Goroutines у Golang. Концептуальна перевага судових процедур полягає в тому, що їх виконання можна розуміти з точки зору потоку управління, явно переходячи вперед і назад між супротинами. Однак ці супроводи не досягають справжнього паралелізму, якщо вони не заплановані в декількох потоках ОС.

Де корисні дешеві заходи? Більшість програмного забезпечення не потребує газільйонних потоків, тому звичайні дорогі потоки зазвичай добре. Однак програмування async іноді може спростити ваш код. Для вільного використання ця абстракція повинна бути достатньо дешевою.

А далі є павутина. Як було сказано вище, павутина за своєю суттю є асинхронною. Мережеві запити просто займають багато часу. Багато веб-серверів підтримують пул потоків, наповнений робочими потоками. Однак більшу частину часу ці потоки будуть простоювати, оскільки вони чекають деякого ресурсу, будь то очікування події вводу-виводу під час завантаження файлу з диска, очікування, поки клієнт не визнає частину відповіді, або чекання, поки база даних запит завершується. NodeJS феноменально продемонстрував, що подальший подій та асинхронний дизайн сервера працює надзвичайно добре. Очевидно, що JavaScript далеко не єдина мова, що використовується для веб-додатків, тому також існує великий стимул для інших мов (помітно в Python та C #) для полегшення асинхронного веб-програмування.


Я рекомендую отримати ваш четвертий останній абзац, щоб уникнути ризику плагіату, він майже такий самий, як і інше джерело, яке я читав. Крім того, маючи порядки менших накладних витрат, ніж потоки, продуктивність виконання процедур не може бути спрощена до "непрямого виклику функції". Дивіться детальну інформацію про збільшені програми впровадження програм тут і тут .
WHN

1
@snb Що стосується запропонованого редагування: GIL може бути деталі реалізації CPython, але основна проблема полягає в тому, що мова Python не має явної моделі пам'яті, яка б визначала паралельну мутацію даних. GIL - це злом для вирішення цих питань. Але реалізація Python із справжнім паралелізмом повинна пройти велику довжину, щоб забезпечити еквівалентну семантику, наприклад, як обговорювалося в книзі Jython . В основному: кожна змінна або об'єктна полівка повинна бути дорогою мінливою змінною.
амон

3
@snb Щодо плагіату: Плагіатство хибно представляє ідеї як власні, особливо в академічному контексті. Це серйозне твердження , але я впевнений, що ви цього не мали на увазі. Пункт "Нитки в основному схожі на процеси" просто повторює відомі факти, про які йдеться в будь-якій лекції чи текстовій книзі про операційні системи. Оскільки існує лише так багато способів, щоб чітко викласти ці факти, я не здивований, що абзац вам звучить звично.
амон

Я не змінив значення, щоб зрозуміти, що в Python була модель пам'яті. Крім того, використання летючої речовини не залежить від самостійної зменшення продуктивності просто означає, що компілятор не може оптимізувати змінну таким чином, що вона може припустити, що змінна буде незмінною з явними операціями в поточному контексті. У світі Jython це може насправді мати значення, оскільки його використовуватиме компіляція VM JIT, але у світі CPython ви не турбуєтесь про оптимізацію JIT, ваші мінливі змінні існуватимуть у просторі виконання інтерпретатора, де оптимізація не може бути здійснена. .
WHN

7

Коротинії були корисними, оскільки операційні системи не виконували попереднього планування. Після того, як вони почали надавати переважне планування, потрібно було періодично відмовлятися від контролю у вашій програмі.

У міру того, як багатоядерні процесори стають більш поширеними, підпрограми використовуються для досягнення паралелізму завдань та / або підтримання високої використання системи (коли одна нитка виконання повинна чекати на ресурсі, інша може почати працювати на своєму місці).

NodeJS - це особливий випадок, коли супроводи використовуються для отримання паралельного доступу до IO. Тобто, декілька потоків використовуються для обслуговування запитів вводу-виводу, але один потік використовується для виконання коду javascript. Мета виконання коду користувача у потоці знака - уникнути необхідності використання мутексів. Це підпадає під категорію спроб підтримати високу використання системи, як було зазначено вище.


4
Але супроводи не керуються ОС. ОС не знає, що таке супровід, на відміну від волокон С ++
переобмін

У багатьох ОС є супроводи.
Йорг W Міттаг

такі функції, як python та Javascript ES6 +, чи не є багатопроцесорними? Як вони досягають паралелізму завдань?
WHN

1
@Mael Нещодавнє "відродження" спільних процедур походить від python та javascript, і вони не досягають паралелізму зі своїми супроводами, як я розумію. Тобто ця відповідь є невірною, оскільки парралізм завдання не є причиною, коли корутини взагалі "повертаються". Також Луас теж не є багатопроцесорним? EDIT: Я щойно зрозумів, що ти не говориш про паралелізм, але чому ти відповів мені в першу чергу? Відповідайте на dlasalle, оскільки явно вони помиляються в цьому.
whn

3
@dlasalle Ні, вони не можуть, незважаючи на те, що він говорить "працює паралельно", що не означає, що будь-який код фізично виконується одночасно. GIL зупинить це, і async не породжує окремі процеси, необхідні для багатопроцесорної обробки в CPython (окремі GIL). Async працює з урожайністю на одній нитці. Коли вони говорять «parralel» , вони на самому ділі означає кілька функцій yeilding для інших функцій роботи і interleving виконання функції. Процеси асинхронізації Python не можна запускати паралельно через impl. Зараз у мене є три мови, які не виконують паралельних процедур, Lua, Javascript та Python.
whn

5

Ранні системи використовували супроводи, щоб забезпечити паралельність насамперед тому, що це найпростіший спосіб зробити це. Нитки вимагають неабиякої підтримки з боку операційної системи (ви можете реалізувати їх на рівні користувача, але вам знадобиться певний спосіб упорядкувати систему, щоб періодично переривати ваш процес), і їх важче реалізувати, навіть якщо у вас є підтримка .

Нитки почали займати пізніше, тому що до 70-х або 80-х всі серйозні операційні системи підтримували їх (а до 90-х навіть Windows), і вони більш загальні. І вони простіші у використанні. Раптом всі подумали, що нитки - це наступна велика справа.

До кінця 90-х тріщини почали з'являтися, і на початку 2000-х років стало очевидним, що існують серйозні проблеми з нитками:

  1. вони споживають багато ресурсів
  2. контекстні комутатори займають багато часу, умовно кажучи, і часто є непотрібними
  3. вони руйнують місце відліку
  4. написання правильного коду, який координує декілька ресурсів, які можуть потребувати виключного доступу, несподівано складно

З часом кількість завдань, які програми, як правило, потрібно виконувати в будь-який час, швидше зростає, збільшуючи проблеми, викликані (1) та (2) вище. Розбіжність між швидкістю процесора та часом доступу до пам'яті зростає, що загострює проблему (3). А складність програм з точки зору кількості та різних видів ресурсів їм зростає, збільшуючи актуальність проблеми (4).

Але втративши трохи загальності та поклавши трохи додаткового навантаження на програміста, щоб подумати про те, як їхні процеси можуть діяти разом, спрощення може вирішити всі ці проблеми.

  1. Для корекцій потрібні трохи більше ресурсів, ніж кілька сторінок для стека, набагато менше, ніж більшість реалізацій потоків.
  2. Розслідування лише перемикають контекст у визначених програмістом пунктах, що, сподіваємось, означає лише тоді, коли це необхідно. Їм також зазвичай не потрібно зберігати стільки контекстної інформації (наприклад, регістрових значень), скільки потоків, тобто кожен перемикач, як правило, швидший, а також потребує їх меншої кількості.
  3. Поширені схеми процедур, включаючи операції типу виробників / споживачів, передають дані між процедурами таким чином, що активно збільшує місцевість. Крім того, контекстні перемикачі, як правило, відбуваються лише між одиницями роботи, що не знаходяться всередині них, тобто в той момент, коли локальність зазвичай все одно зведена до мінімуму.
  4. Блокування ресурсів рідше буде необхідним, коли підпрограми знають, що їх не можна довільно переривати в середині операції, що дозволяє простішим програмам працювати правильно.

5

Передмова

Я хочу почати з того, щоб вказати причину, чому в супротивах не виникає відродження, паралелізму. Взагалі сучасні заходи не є засобом досягнення паралелізму, заснованого на завданнях, оскільки сучасні реалізації не використовують функцію багатопроцесорної обробки. Найближче до вас дістаються такі речі, як волокна .

Сучасне використання (чому вони повернулися)

Сучасні спротиви стали способом досягти ледачої оцінки - щось дуже корисне у функціональних мовах, як haskell, де, замість того, щоб повторювати весь набір для виконання операції, ви змогли б виконати лише оцінку операції настільки, наскільки це потрібно ( корисний для нескінченних наборів предметів або в іншому випадку великих наборів із достроковим припиненням та підмножинами).

Використання ключового слова Yield для створення генераторів (які самі по собі задовольняють частину ледачих потреб в оцінці) на таких мовах, як Python та C #, спрощення, в сучасній реалізації були не тільки можливими, але й можливими за допомогою спеціального синтаксису в самій мові. (хоча python врешті-решт додав декілька біт для допомоги). Спільні процедури допомагають при ледачій евакуляції з ідеєю майбутніх s, якщо, якщо вам не знадобиться значення змінної на той час, ви можете затримати фактичне її отримання, поки явно не попросите це значення (дозволяючи використовувати значення та ліниво оцінювати це в інший час, ніж миттєвість).

Окрім лінивої оцінки, хоча, особливо у веб-сфері, ці спільні процедури допомагають виправити пекельний зворотний дзвінок . Програми стануть корисними у доступі до бази даних, трансакціях в Інтернеті, інтерфейсі тощо, де час обробки на клієнтській машині не приведе до швидшого доступу до того, що вам потрібно. Нитка може заповнити те саме, але вимагає набагато більше накладних витрат у цій сфері, і на відміну від судових процедур, насправді корисні для паралелізму завдань .

Коротше кажучи, по мірі розвитку веб-сайтів і функціональних парадигм більше зливаються з імперативними мовами, розробки як рішення для асинхронних проблем і лінивої оцінки. Порушення надходять у проблемні простори, де багатопроцесові нарізки та нарізки загалом або непотрібні, незручні або неможливі.

Сучасний приклад

Усі функції таких мов, як Javascript, Lua, C # і Python, отримують їх реалізацію окремими функціями, які відмовляються від управління основною темою для інших функцій (нічого спільного з викликами операційної системи).

У цьому прикладі пітона ми маємо смішну функцію пітона з чимось, що називається awaitвсередині нього. Це в основному вихід, який дає виконання, loopяке потім дозволяє виконувати іншу функцію (в даному випадку іншу factorialфункцію). Зверніть увагу , що , коли він говорить : «Паралельне виконання завдань» , що є неправильним, він фактично не виконує паралельно, його чергуванням виконання функції через використання AWAIT ключового слова (що мати на увазі , це просто особливий тип виходу)

Вони дозволяють отримати єдиний, не паралельний, контроль над одночасними процесами, що не є завданням паралельним , в тому сенсі, що ці завдання не виконуються ніколи одночасно. Розробки не є нитками в сучасних мовних реалізаціях. Усі ці мовні програми спільної роботи виходять із цих викликів функціональних викликів (які ви, власне, програмісту повинні вводити вручну у ваші спільні процедури).

EDIT: C ++ Boost coroutine2 працює так само, і їхнє пояснення повинно дати кращу картину того, про що я говорю з yeilds, дивіться тут . Як бачите, немає "особливого випадку" з реалізаціями, такі речі, як прискорені волокна, є винятком із правила, і навіть тоді потрібна явна синхронізація.

EDIT2: оскільки хтось думав, що я говорю про систему на основі завдань c #, я не був. Я говорив про систему Unity та наївні реалізації c #


@ T.Sar Я ніколи не заявляв, що C # не мав жодних "природних" процедур, а також C ++ (можливо, змінився), а також python (і він все ще мав їх), і всі три мають спільну реалізацію. Але всі C # реалізації корутин (на зразок тих, що знаходяться в єдності) базуються на врожайності, як я описую. Також ваше використання "хак" тут безглуздо, я вважаю, що кожна програма є злому, оскільки вона не завжди була визначена мовою. Я жодним чином не змішую C # "систему завдань на основі завдань" ні з чим, я навіть не згадував про це.
whn

Я б запропонував зробити вашу відповідь трохи більш зрозумілою. C # має як концепцію інструкцій, що чекають, так і систему паралелізму на основі завдань - використання C # і цих слів, наводячи приклади на python про те, як python насправді не є справді паралельним, може спричинити багато сумнівів. Також видаліть своє перше речення - не потрібно безпосередньо атакувати інших користувачів у такій відповіді.
Т. Сар - Відновіть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.