Большая часть основы для сопрограмм произошла в 60-х / 70-х годах, а затем остановилась в пользу альтернатив (например, темы)
Есть ли какая-то субстанция для возобновления интереса к сопрограммам, возникающего на питоне и других языках?
python
multithreading
concurrency
multitasking
user1787812
источник
источник
Ответы:
Сопрограммы никогда не уходили, их просто затмили другие вещи. В последнее время возрос интерес к асинхронному программированию и, следовательно, к сопрограммам, во многом благодаря трем факторам: более широкому принятию методов функционального программирования, наборам инструментов с плохой поддержкой истинного параллелизма (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 #) есть большой стимул для упрощения асинхронного веб-программирования.
источник
Сопрограммы были полезны, потому что операционные системы не выполняли упреждающее планирование. Как только они начали предоставлять упреждающее планирование, больше не нужно было периодически отказываться от контроля над вашей программой.
Поскольку многоядерные процессоры становятся более распространенными, сопрограммы используются для достижения параллелизма задач и / или поддержания высокого уровня использования системы (когда один поток выполнения должен ожидать ресурс, другой может начать работать вместо него).
NodeJS - это особый случай, когда используемые сопрограммы получают параллельный доступ к IO. Таким образом, несколько потоков используются для обслуживания запросов ввода-вывода, но один поток используется для выполнения кода JavaScript. Цель выполнения пользовательского кода в потоке подписи состоит в том, чтобы избежать необходимости использовать мьютексы. Это относится к категории попыток поддерживать высокий уровень использования системы, как упомянуто выше.
источник
Ранние системы использовали сопрограммы для обеспечения параллелизма прежде всего потому, что они - самый простой способ сделать это. Потоки требуют достаточного количества поддержки со стороны операционной системы (вы можете реализовать их на уровне пользователя, но вам потребуется какой-то способ организации системы, чтобы периодически прерывать ваш процесс), и их сложнее реализовать, даже если у вас есть поддержка ,
Потоки начали переходить позже, потому что к 70-м или 80-м годам их поддерживали все серьезные операционные системы (а к 90-м даже Windows), и они стали более общими. И они проще в использовании. Внезапно все подумали, что темы были следующей большой вещью.
К концу 90-х годов начали появляться трещины, и в начале 2000-х годов стало очевидно, что существуют серьезные проблемы с потоками:
Со временем число задач, которые программы обычно должны выполнять в любое время, быстро росло, увеличивая проблемы, вызванные (1) и (2) выше. Несоответствие между скоростью процессора и временем доступа к памяти увеличивается, усугубляя проблему (3). А сложность программ с точки зрения того, сколько и каких видов ресурсов им требуется, возрастает, что повышает актуальность проблемы (4).
Но, потеряв некоторую общность и возложив на программиста небольшую дополнительную нагрузку на размышления о том, как их процессы могут работать вместе, сопрограммы могут решить все эти проблемы.
источник
Предисловие
Я хочу начать с указания причины, по которой сопрограммы не получают возрождения, параллелизма. В общем, современные сопрограммы не являются средством для достижения параллелизма на основе задач, поскольку современные реализации не используют многопроцессорную функциональность. Самое близкое, что вы получаете к этому, это такие вещи, как волокна .
Современное использование (почему они вернулись)
Современные сопрограммы стали способом достижения ленивых вычислений , что очень полезно в функциональных языках, таких как 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 #
источник