Единство сопрограммы против потоков


13

Какая разница между сопрограммой и потоком? Есть ли преимущества использования одного над другим?


3
Сопрограммы работают над основным потоком.
Woltus


2
Вы не можете использовать любые функции Unity в потоке. Сопрограммы могут.
Hellium


1
@Hellium Correction, вы не можете использовать любые функции Unity в другом потоке, т.е. функции Unity можно использовать в основном потоке .
Pharap

Ответы:


24

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

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

С потоками, с другой стороны, вы абсолютно не знаете, в каком состоянии находится основной цикл Unity в данный момент (фактически он вообще может больше не работать). Таким образом, ваша нить может вызвать немало хаоса, если делать что-то в то время, когда она не должна этого делать. Не трогайте какие-либо собственные функции Unity из подпотока . Если вам необходимо установить связь между подпотоком и вашим основным потоком, попросите поток записать в какой-нибудь потокобезопасный (!) Контейнер-объект и попросите MonoBehaviour прочитать эту информацию во время обычных функций событий Unity.

Недостаток отсутствия «реальной» многопоточности состоит в том, что вы не можете использовать сопрограммы для распараллеливания интенсивных вычислений на нескольких процессорных ядрах. Вы можете использовать их, однако, чтобы разделить расчет на несколько обновлений. Поэтому вместо того, чтобы заморозить игру на одну секунду, вы просто получите более низкую среднюю частоту кадров в течение нескольких секунд. Но в этом случае вы несете ответственность заyield сопрограммой всякий раз, когда хотите разрешить Unity запускать обновление.

Вывод:

  • Если вы хотите использовать асинхронное выполнение для выражения логики игры, используйте сопрограммы.
  • Если вы хотите использовать асинхронное выполнение для использования нескольких ядер ЦП, используйте потоки.

Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
MichaelHouse

10

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

Потоки - это то, что мы называем «вытесняющая многозадачность». Когда вы используете потоки, фреймворк оставляет за собой право на любой момент остановить ваш поток в середине и переключиться на другой поток. Неважно, где вы находитесь. Вы даже можете быть остановлены на полпути путем записи переменной в память в некоторых случаях!

Есть плюсы и минусы для каждого. Минусы сопрограмм, вероятно, легче всего понять. Во-первых, сопрограммы все выполняются на одном ядре. Если у вас четырехъядерный процессор, сопрограммы будут использовать только одно из четырех ядер. Это упрощает вещи, но может быть проблемой производительности в некоторых случаях. Второй недостаток заключается в том, что вы должны знать, что любая сопрограмма может остановить всю вашу программу, просто отказавшись yield. Это было проблемой на Mac OS9 много лет назад. OS9 поддерживает только совместную многозадачность на всем компьютере. Если одна из ваших программ зависнет, она может так сильно остановить компьютер, что ОС не сможет даже отобразить текст сообщения об ошибке, чтобы вы знали, что произошло!

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

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

Причина такой дополнительной сложности на самом деле очень проста. Модель вытесняющей многозадачности действительно похожа на модель многопоточности, которая позволяет вам не только прерывать другие потоки, но и фактически запускать потоки рядом на разных ядрах. Это невероятно мощный инструмент, который является единственным способом действительно использовать эти новые четырехъядерные и шестнадцатеричные процессоры, которые выходят, но открывают коробку pandoras. Правила синхронизации того, как управлять этими данными в многопоточном мире, явно брутальны. В мире C ++ есть целые статьи, посвященные тому, MEMORY_ORDER_CONSUMEчто представляет собой один из немногих, многопоточных способов синхронизации.

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

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

Комментарий Кандид Мун, чтобы придерживаться сопрограмм, также является моей рекомендацией. Если вы действительно хотите силы потоков, то сделайте это. Выйди и действительно изучать темы, формально. У нас было несколько десятилетий, чтобы выяснить, как организовать лучший способ думать о потоках, чтобы вы могли получать безопасные надежные повторяемые результаты на ранних этапах и повышать производительность по мере продвижения. Например, все вменяемые курсы будут обучать мьютексам перед тем, как учить переменные условия. Все вменяемые курсы, которые охватывают атомистику, будут полностью обучать взаимным исключениям и переменным состояниям, даже не упоминая, что атомика существует. (Примечание: не существует такого понятия, как вменяемый учебник по атомной физике.) Попробуйте научиться многопоточности, и вы умоляете о мигрени.


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

... но просто способ для потоков, которые обрабатывают y () и z (), ждать, пока они не будут запущены, и затем способ для основного потока ждать, после выполнения x (), до y () и z () завершено.
суперкат

@supercat У вас всегда должна быть некоторая синхронизация для перехода между независимыми параллельными фазами и последовательными фазами. Иногда это не что иное, как что-то join(), но вам что-то нужно. Если у вас нет архитектора, который разработал систему для синхронизации, вы должны написать это самостоятельно. Как человек, который занимается многопоточными вещами, я обнаружил, что умственные модели работы компьютеров нужно корректировать, прежде чем они будут выполнять синхронизацию безопасно (именно этому будут учить хорошие курсы)
Корт Аммон,

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

О новых типах ошибок: обратите внимание также, что если вы не можете логически доказать, что код безопасен, невозможно узнать, насколько он будет ошибочным. Хотя порядок выполнения может быть неопределенным, он часто использует похожие пути каждый раз, когда вы запускаете его на 1 машине. Вы часто обнаруживаете, что он терпит неудачу и часто на небольшом количестве машин, и вы не можете провести тестирование на всех возможных машинах. На работе у нас была ошибка, которая проявлялась только на 1 из наших машин, только если логирование было отключено; потребовалось много времени, чтобы выследить это. Не хочу этого в дикой природе. Не просто проверяйте синхронизацию, логически докажите это.
Аарон

2

В самых простых сроках возможно ...


Потоки

Поток не решает, когда он выдаст, операционная система («ОС», например, Windows) решает, когда будет получен поток. Операционная система почти полностью отвечает за планирование потоков, она решает, какие потоки запускать, когда их запускать и как долго.

Кроме того, поток может выполняться синхронно (один поток за другим) или асинхронно (разные потоки работают на разных ядрах ЦП). Возможность асинхронного запуска означает, что потоки могут выполнять больше работы за одно и то же время (потому что потоки буквально выполняют две вещи одновременно). Даже синхронные потоки выполняют большую работу, если ОС хорошо их планирует.

Однако эта дополнительная вычислительная мощность сопровождается побочными эффектами. Например, если два потока пытаются получить доступ к одному и тому же ресурсу (например, списку) и каждый поток может быть произвольно остановлен в любой точке кода, изменения второго потока могут мешать изменениям, выполненным первым потоком. (См. Также: Условия гонки и тупик .)

Потоки также считаются «тяжелыми», потому что они имеют много накладных расходов, а это означает, что при переключении потоков происходит значительная потеря времени.


Сопрограммы

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

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

Сопрограммы, как правило, не требуют переключения контекста, поэтому они быстро включаются и выключаются и довольно легки.


В итоге:

Нить:

  • Синхронный или Асинхронный
  • Уступлено ОС
  • Дается случайно
  • тяжелый

сопрограммная:

  • синхронный
  • Урожай себя
  • Урожайность по выбору
  • облегченный

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.