[ПЕРЕВОД] Метутисторный профиль: шаг за шагом ближе к миру многопоточных

задняя часть Программа перевода самородков

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

编写能够利用这种特性的软件会很有意思,但也很棘手:这需要你理解计算机背后所发生的事情。在第一节中,我将会试着简单覆盖关于线程的知识,它是由操作系统提供能实现这种魔术的工具之一。 Давайте начнем!

Процессы и потоки: правильно называть вещи

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

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

Например, ваш проигрыватель может работать в нескольких потоках: один поток для рендеринга пользовательского интерфейса — обычно основной поток, другой — для воспроизведения музыки и т. д.

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

进程 vs 线程

Рис. 1. Операционную систему можно рассматривать как блок, содержащий процесс, а процесс можно рассматривать как содержащий один или несколько потоков блока.

Разница между процессом и потоком

У каждого процесса есть свой блок памяти, который выделяется операционной системой. По умолчанию процессы не могут делиться друг с другом блоками памяти: программы-браузеры не могут получить доступ к памяти, выделенной плееру, и наоборот. Даже если вы запускаете один и тот же экземпляр процесса (например, дважды запускаете браузер), между ними нет общей памяти. Операционная система рассматривает каждый экземпляр как новый процесс и выделяет собственную независимую память. Итак, как правило, несколько процессов не могут обмениваться данными друг с другом, если только они не используют некоторые продвинутые приемы — так называемыемежпроцессного взаимодействия.

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

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

зеленая нить, волокно

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

Зеленые потоки можно создавать и управлять ими быстрее, поскольку операции с ними полностью обходят операционную систему, но это имеет недостатки. Я коснусь этой темы в следующем разделе.

Название «зеленые потоки» происходит от имени зеленой команды Sun Microsystems, которая разработала исходную библиотеку потоков для Java в 1990-х годах. Теперь Java больше не использует зеленые потоки: они были переведены на собственные потоки в 2000 году. Другие языки программирования, такие как Go, Haskell или Ruby — используют ту же реализацию, что и зеленые потоки без нативных потоков.

Для чего нужна нить?

Почему процесс должен использовать несколько потоков? Как я упоминал ранее, параллельная обработка может значительно ускорить работу. Предположим, вы хотите отрендерить фильм в редакторе фильмов. Если редактор достаточно умен, он может распределять операции рендеринга по нескольким потокам, каждый из которых обрабатывает часть фильма. Таким образом, если задача занимает час с одним потоком, она займет 30 минут с двумя потоками, 15 минут с четырьмя потоками и так далее.

Это действительно так просто? Здесь нужно учитывать три вещи:

  1. Не каждая программа нуждается в многопотативной. Если ваша заявка выполняется последовательной операцией или дождитесь, что пользователь что-то сделать, многопоточь может быть не так хорош;
  2. Вы не можете просто добавить больше потоков в вашем приложении, пусть он будет работать быстрее: каждый подтесмент должен быть тщательно продуманным и предназначен для реализации параллельных операций;
  3. Не может 100% гарантировать, что поток действительно параллельно выполняет выполнение операций (т.е.выполнять одновременно): это действительно зависит от основного оборудования, на котором работает программа.

И последнее, но не менее важное: если ваш компьютер не поддерживает несколько операций одновременно, операционная система будет делать вид, что они выполняются именно так. Мы увидим это вскоре после этого. А пока давайте понимать параллелизм какмы смотримЗадача выполняется одновременно, а настоящий параллелизм — это как буквально, и задача выполняется одновременно.

并发 vs 并行

Рис. 2. Параллелизм — это разновидность параллелизма.

Что делает возможным параллелизм и параллелизм

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

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

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

Современные процессоры имеют несколько ядер, каждое из которых одновременно выполняет независимую операцию. Это означает, что настоящий параллелизм возможен с несколькими ядрами. Например, мой процессор Intel Core i7 имеет 4 ядра: он может одновременно запускать 4 разных процесса и потока.

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

Многопоточные приложения, работающие на одном ядре: есть ли смысл?

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

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

Давайте переосмыслим ваше приложение с многопоточным подходом. Поток программы отвечает за доступ к диску, поток B отвечает за основной интерфейс. Если поток A замедляется из-за того, что устройство читает и зависает, поток B все равно запускает основной интерфейс, чтобы ваши приложения оставались отзывчивыми. Это возможно, потому что два потока, операционная система может выделять ресурсы ЦП, чтобы переключаться между ними, не давая программе из-за более медленного потока и зависания.

Чем больше тем, тем больше проблем

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

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

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

  • Состояние гонки — поток чтения не должен иметь возможности читать память до тех пор, пока поток записи не завершит запись. Что, если бы все произошло в обратном порядке? Более тонкое, чем гонка данных, состояние гонки — это когда несколько потоков выполняют свою работу в непредсказуемом порядке, тогда как на самом деле мы хотим, чтобы эти операции выполнялись в правильном порядке. Даже при наличии защиты от гонки данных ваша программа все равно может вызвать состояние гонки.

Концепция безопасности потоков

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

Основная причина гонки данных

Мы знаем, что ядро ​​ЦП может одновременно выполнять только одну машинную инструкцию. Такая инструкция называется атомарной операцией, потому что она неделима: ее нельзя разбить на более мелкие операции. Греческое слово «атом» (ἄτομος; атомос) означаетнельзя разделить.

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

К сожалению, большинство операций не являются атомарными. На некоторых аппаратных средствах даже что-то вродеx = 1Такая простая операция присваивания может также состоять из нескольких атомарных машинных инструкций, что делает всю операцию присваивания неатомарной операцией. Если поток читаетxГонка данных запускается, когда другой поток присваивает ему значение.

Основная причина состояния гонки

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

writer_thread.start()
reader_thread.start()

Запустите программу несколько раз, вы заметите, что поведение каждого прогона - это то, как он отличается: иногда пишите резьбу первого запуска, иногда прочитайте FiTe First Start. Если вам нужно написать программу перед первым чтением, она обязательно столкнуться с конкуренцией.

Такое поведение называется недетерминизмом: результат выполнения будет меняться каждый раз, когда вы не можете его предсказать. Отладка программ, затронутых условиями гонки, очень раздражает, потому что вы не всегда можете воспроизвести проблему контролируемым образом.

Чтобы научить потоки ладить: контроль параллелизма

И гонки данных, и условия гонки — это реальные проблемы: некоторые люди дажеумереть из-за этого. Искусство планирования нескольких одновременных потоков называется контролем параллелизма: для решения этой проблемы операционные системы и языки программирования предлагают несколько решений. Наиболее важными из них являются:

  • Синхронизация — способ гарантировать, что ресурс используется только одним потоком за раз. Синхронизация заключается в том, чтобы пометить определенную часть кода как «защищенную», чтобы несколько параллельных потоков не могли выполнять код одновременно, и чтобы они не испортили общие данные;

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

  • Неизменяемые данные — общие данные помечаются как неизменяемые, ничто не может их изменить: потоки могут только читать из них, что устраняет первопричину. Как мы знаем, поток может безопасно считывать данные из одной и той же области памяти, если он не модифицирует память. Этофункциональное программированиеОсновная идея, стоящая за ним.

В следующем разделе этой небольшой серии статей о параллелизме я рассмотрю все эти увлекательные темы. Быть в курсе!

Ссылаться на

8 bit avenue - Difference between Multiprogramming, Multitasking, Multithreading and Multiprocessing
Wikipedia - Inter-process communication
Wikipedia - Process (computing)
Wikipedia - Concurrency (computer science)
Wikipedia - Parallel computing
Wikipedia - Multithreading (computer architecture)
Stackoverflow - Threads & Processes Vs MultiThreading & Multi-Core/MultiProcessor: How they are mapped?
Stackoverflow - Difference between core and processor?
Wikipedia - Thread (computing)
Wikipedia - Computer multitasking
Ibm.com - Benefits of threads
Haskell.org - Parallelism vs. Concurrency
Stackoverflow - Can multithreading be implemented on a single processor system?
HowToGeek - CPU Basics: Multiple CPUs, Cores, and Hyper-Threading Explained
Oracle.com - 1.2 What is a Data Race?
Jaka's corner - Data race and mutex
Wikipedia - Thread safety
Preshing on Programming - Atomic vs. Non-Atomic Operations
Wikipedia - Green threads
Stackoverflow - Why should I use a thread vs. using a process?

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.