Почему сопрограммы вернулись? [закрыто]


19

Большая часть основы для сопрограмм произошла в 60-х / 70-х годах, а затем остановилась в пользу альтернатив (например, темы)

Есть ли какая-то субстанция для возобновления интереса к сопрограммам, возникающего на питоне и других языках?



9
Я не уверен, что они когда-либо уходили.
Blrfl

Ответы:


26

Сопрограммы никогда не уходили, их просто затмили другие вещи. В последнее время возрос интерес к асинхронному программированию и, следовательно, к сопрограммам, во многом благодаря трем факторам: более широкому принятию методов функционального программирования, наборам инструментов с плохой поддержкой истинного параллелизма (JavaScript! Python!) И, что наиболее важно: различным компромиссам между потоками и сопрограммами. Для некоторых случаев использования сопрограммы объективно лучше.

Одна из самых больших программных парадигм 80-х, 90-х годов и сегодня - ООП. Если мы посмотрим на историю ООП и, в частности, на развитие языка Simula, мы увидим, что классы развивались из сопрограмм. 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 на Голанге. Концептуальное преимущество сопрограмм состоит в том, что их выполнение можно понимать с точки зрения потока управления, явно проходящего между сопрограммами и обратно. Однако эти сопрограммы не достигают истинного параллелизма, если они не запланированы для нескольких потоков ОС.

Где дешевые сопрограммы полезны? Большинству программ не нужны потоки gazillion, поэтому обычные дорогие потоки обычно в порядке. Однако асинхронное программирование иногда может упростить ваш код. Чтобы абстракция использовалась свободно, она должна быть достаточно дешевой.

И тогда есть сеть. Как упомянуто выше, сеть по своей сути асинхронна. Сетевые запросы просто занимают много времени. Многие веб-серверы поддерживают пул потоков, полный рабочих потоков. Однако большую часть своего времени эти потоки будут работать вхолостую, потому что они ожидают некоторого ресурса, будь то ожидание события ввода-вывода при загрузке файла с диска, ожидание, пока клиент не подтвердит часть ответа, или ожидание, пока база данных запрос завершен. NodeJS феноменально продемонстрировал, что последовательное проектирование на основе событий и асинхронный сервер работают очень хорошо. Очевидно, что JavaScript далеко не единственный язык, используемый для веб-приложений, поэтому для других языков (заметных в Python и C #) есть большой стимул для упрощения асинхронного веб-программирования.


Я бы порекомендовал использовать ваш четвертый или последний абзац, чтобы избежать риска плагиата, он почти такой же, как и другой источник, который я читал. Кроме того, несмотря на то, что накладные расходы на порядок меньше, чем у потоков, производительность сопрограмм нельзя упростить до «косвенного вызова функции». См. Раздел Повышение подробностей реализации сопрограмм здесь и здесь .
17

1
@snb Что касается предложенного вами редактирования: GIL может быть деталью реализации CPython, но фундаментальная проблема заключается в том, что язык Python не имеет явной модели памяти, которая задает параллельную мутацию данных. GIL - это способ обойти эти проблемы. Но реализации Python с истинным параллелизмом должны пройти большую длину, чтобы обеспечить эквивалентную семантику, например, как обсуждалось в книге по Jython . В основном: каждая переменная или поле объекта должны быть дорогой изменчивой переменной.
amon

3
@snb Относительно плагиата: Плагиат ложно представляет идеи как ваши собственные, особенно в академическом контексте. Это серьезное утверждение , но я уверен, что вы не это имели в виду. В параграфе «Потоки в основном похожи на процессы» просто повторяются общеизвестные факты, о которых говорится в любой лекции или учебнике об операционных системах. Поскольку существует очень много способов кратко сформулировать эти факты, я не удивлен, что абзац звучит знакомо вам.
Амон

Я не изменил значение, означает , что Python действительно есть модель памяти. Кроме того, использование volatile само по себе не снижает производительность volatile, просто означает, что компилятор не может оптимизировать переменную так, как он может предполагать, что переменная останется неизменной без явных операций в текущем контексте. В мире Jython это может действительно иметь значение, поскольку будет использоваться компиляция VM JIT, но в мире CPython вы не беспокоитесь об оптимизации JIT, ваши изменчивые переменные будут существовать в пространстве времени выполнения интерпретатора, где никакие оптимизации не могут быть сделаны ,
WHN

7

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

Поскольку многоядерные процессоры становятся более распространенными, сопрограммы используются для достижения параллелизма задач и / или поддержания высокого уровня использования системы (когда один поток выполнения должен ожидать ресурс, другой может начать работать вместо него).

NodeJS - это особый случай, когда используемые сопрограммы получают параллельный доступ к IO. Таким образом, несколько потоков используются для обслуживания запросов ввода-вывода, но один поток используется для выполнения кода JavaScript. Цель выполнения пользовательского кода в потоке подписи состоит в том, чтобы избежать необходимости использовать мьютексы. Это относится к категории попыток поддерживать высокий уровень использования системы, как упомянуто выше.


4
Но сопрограммы не управляются ОС. ОС не знает, что такое сопрограмма, в отличие от волокон C ++
переобмена

Многие ОС имеют сопрограммы.
Йорг Миттаг

сопрограммы, такие как Python и Javascript ES6 +, не являются многопроцессорными, хотя? Как они достигают параллелизма задач?
WHN

1
@Mael Недавнее «возрождение» сопрограмм происходит от python и javascript, которые, как я понимаю, не достигают параллелизма со своими сопрограммами. То есть этот ответ неверен, так как параллизм задач не является причиной, по которой сопрограммы «возвращаются» вообще. Кроме того, Луас также не является многопроцессорным? РЕДАКТИРОВАТЬ: Я только что понял, что вы не говорили о параллелизме, но почему вы ответили мне в первую очередь? Ответ dlasalle, поскольку они явно ошибаются по этому поводу.
WHN

3
@dlasalle Нет, они не могут, несмотря на то, что в нем говорится «работает параллельно», что не означает, что любой код выполняется одновременно физически. GIL остановит это, и async не создаст отдельные процессы, необходимые для многопроцессорной обработки в CPython (отдельные GIL). Async работает с выходами в одном потоке. Когда они говорят «parralel», они на самом деле означают несколько функций, уступающих другим функциям работы и чередующихся при выполнении функции. Асинхронные процессы Python не могут выполняться параллельно из-за импл. Теперь у меня есть три языка, которые не выполняют сопрограммы parralel: 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 в конечном итоге добавил несколько битов, чтобы помочь). Co-процедуры помогают с ленивым evaulation с идеей будущего s , где , если вам не нужно значение переменной в то время, вы можете задержать на самом деле его приобретения , пока вы явно не просите этого значения ( что позволяет использовать значение и лениво оцениваю это в другое время, чем экземпляр).

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

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

Современный пример

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

В этом примере Python у нас есть забавная функция Python с чем-то, что вызывается awaitвнутри нее. Это в основном выход, который дает выполнение тому, loopкоторый затем позволяет запускать другую функцию (в данном случае, другую factorialфункцию). Обратите внимание, что когда он говорит «Параллельное выполнение задач», что является неправильным, он на самом деле не выполняется параллельно, а выполняет функцию чередования с использованием ключевого слова await (помните, что это просто особый тип yield)

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

РЕДАКТИРОВАТЬ: C ++ Boost coroutine2 работает так же, и их объяснение должно дать лучшее представление о том, о чем я говорю с уроками, см. Здесь . Как видите, в реализациях нет «особого случая», такие вещи, как повышающие волокна, являются исключением из правила и даже в этом случае требуют явной синхронизации.

РЕДАКТИРОВАТЬ 2: так как кто-то думал, что я говорил о системе на основе задач 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.