Многопоточность — это то, как программа использует процессор компьютера для повышения производительности, эти улучшения производительности приходят в форме того, что называется параллельной обработкой. Параллельная обработка — это когда задачи обрабатываются процессором в цикле, где эти задачи не являются целыми программами, а могут быть подзадачами, которые называются потоками.
Когда программист пишет программу и выполняет ее, код программы и метаданные выделяются операционной системой в собственное пространство памяти, кроме того, программа теперь называется процессом. Процессы — это задания операционной системы, которые остаются в пуле заданий, ожидая выполнения процессором компьютера. Многоядерные процессоры могут выполнять несколько заданий одновременно, поскольку у них несколько ядер, ядра многоядерного процессора подобны полосам на шоссе. , чем больше у вас ядер, тем больше вы можете делать параллельно в любой момент времени, этот параллелизм называется параллельной обработкой и отличается от параллельной обработки.
Параллельная обработка — это когда ядро процессора быстро переключается между двумя потоками выполнения из-за задействованной скорости, и пользователю может показаться, что компьютер выполняет две задачи параллельно, с другой стороны, параллельная обработка когда мы расширяем возможности параллельной обработки, добавляя к процессору больше ядер, таким образом, поскольку каждое ядро может работать независимо, мы можем добиться фактического параллелизма. Параллельная обработка также возможна на компьютерах с несколькими процессорами.
ЦП или процессор в компьютере имеет дело с потоками выполнения, называемыми просто потоками. Потоки могут быть одного из двух типов: потоки на уровне пользователя и потоки на уровне ядра. Потоки на уровне пользователя создаются и управляются на уровне пользователя с помощью библиотеки потоков. Программисты используют эти библиотеки для реализации функций потоков в своих программах. С другой стороны, потоки создаются и управляются ядром операционной системы, отсюда и название.
По умолчанию процессы являются однопоточными, то есть они могут выполнять только одну задачу за раз, но многопоточные процессы могут выполнять несколько задач одновременно. Когда процесс покидает пул заданий, ядро предлагает ему потоки уровня ядра, потоки уровня пользователя процесса по очереди используют свои потоки уровня ядра, в то время как потоки уровня ядра по очереди используют ядра ЦП. это то, что потоки уровня ядра также называются виртуальными процессорами. Пользовательские потоки сопоставляются с потоками ядра с помощью 3 различных моделей: Модель «один к одному», в которой ровно один пользовательский поток сопоставляется ровно с одним потоком ядра. Модель «многие к одному», в которой множество пользовательских потоков сопоставляются ровно с одним потоком ядра. И модель «многие ко многим», в которой многие пользовательские потоки сопоставляются со многими потоками ядра.
В модели «один к одному» пользовательские потоки процесса могут продолжать выполнение, даже если один из них выдает блокирующий вызов, например чтение из файла, и это потому, что каждый пользовательский поток сопоставляется со своим собственным потоком ядра. В модели «многие к одному», когда пользовательский поток выдает блокирующий вызов, остальные вынуждены ждать, потому что все они сопоставлены с одним и тем же потоком ядра. Наконец, модель «многие ко многим» является наиболее эффективной из трех, если пользовательский поток выдает блокирующий вызов, другие не затрагиваются и, таким образом, продолжают выполнение, по очереди подключаясь к другим потокам ядра, которые не заблокированы.
Это сопоставление между пользовательскими потоками и потоками ядра необходимо для выполнения процесса, поскольку операционная система знает только о потоках ядра, а потоки ядра — это то, что в конечном итоге выполняет процессор.
Потоки процесса делят память между собой, именно по этой причине они лучше подходят для параллельного программирования, чем создание нескольких процессов одного и того же приложения. Процессы не разделяют память, более того, процессы тяжелее, чем потоки, межпроцессное взаимодействие возможно, но медленнее и сложнее, чем межпоточное взаимодействие. Здесь также стоит отметить, что, когда я говорю, что потоки процесса совместно используют одну и ту же память, я имею в виду то же пространство памяти, что и их корневой процесс, потоки процесса не могут получить доступ к пространствам памяти других процессов.
Гонки данных и условия гонки — это две ловушки, которых следует избегать при разработке многопоточного кода. Ошибка гонки данных возникает, когда два потока процесса одновременно обращаются к ячейке общей памяти, если один из потоков записывает в ячейку памяти, в то время как другой читает из нее, данные повреждаются, и это называется проблемой гонки данных. Когда два потока процесса должны получить доступ к ячейке памяти в указанном порядке, возможно, что этот порядок будет обратным, в этом случае мы говорим о проблеме Race Condition.
Синхронизация и неизменяемые данные — это два способа достижения контроля параллелизма. Синхронизация потоков означает применение взаимного исключения при доступе к общей памяти. Таким образом, потоки получают блокировки и освобождают их после завершения изменения определенного участка памяти, другими словами, только один поток. может получить доступ к местоположению в то время. Неизменяемые данные — это данные, которые нельзя изменить. Создание неизменяемых данных в общей памяти делает параллелизм более безопасным, поскольку потоки могут только читать из этого места и не могут записывать в него. Существует еще один метод управления параллелизмом, который заключается в использовании атомарных инструкций. Атомарные инструкции либо полностью выполняются процессором, либо не выполняются вообще, из-за этого они потокобезопасны по своей природе, языки высокого уровня не поддерживаются. atomic, назначение переменной, например, может быть разделено как минимум на 2 атомарных шага: выборка переменной x. Запишите 1 в x, если поток A выполняет назначение, он может быть прерван на полпути другим потоком B, который, например, запишет 2 в x, затем поток A вернется и завершит свою операцию, записав 1 в x, если это программу подсчета, то она явно не удалась. Если бы каждый поток обрабатывал одну атомарную операцию, это решило бы проблему.
Многопоточность повышает эффективность программы, однако не каждая программа должна быть многопоточной.