Работа с процессами и потоками имеет много общих моментов, но и различий у них немало. В основном, эти различия кроются в самой сути этих понятий. Давайте рассмотрим для начала каждый из них подробней.
Процесс – это некоторая часть работы ОС, обладающая уникальным идентификационным номером – id, и адресное пространство. Адресное пространство – некоторый список адресов в памяти, с которыми происходит работа этого процесса. С другими адресами процессу приходится работать через системный вызов. Одна программа может включать как несколько процессов, так и один, причем последнее используется наиболее часто. Разбиение на процессы позволяет распараллелить задачи, благодаря чему ускорить работу, но в большинстве случаев для этого проще и выгоднее использовать потоки, которые намного быстрее взаимодействуют друг с другом и обладают рядом других положительных моментов, что и привело к меньшей используемости процессов.
В C++ работать с процессами можно посредством подключения различных специализированных библиотек, к примеру Toolhelp. Для примера, вызвать список всех процессов с помощью Toolhelp можно одной строкой
Что такое многопоточность/multithreading? Введение в процессы и потоки
HANDLE pSnap = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS , 0 ) ;
Сама функция просто просматривает все процессы в системе, и добавляет список согласно 2-ому параметру функции, означающий условия для добавления (т.к. стоит 0, то все процессы включаются).
Поток – это часть уже самого процесса, выполняющая определенный список действий. У каждого процесса есть как минимум один поток, и их увеличение обеспечивает распараллеливание процесса. Чем выгодней такое увеличение потоков внутри процесса увеличения количества самих процессов?
Каждый поток, как часть процесса, имеет доступ ко всему адресному пространству процесса, ко всем его устройствам и переменным. Поэтому взаимодействие двух отдельных потоков реализуется очень просто и не требует обращения к системе. По этой причине использование потоков более распространено, чем процессов.
Сам поток представляет собой стек команд со счетчиком, обладающий несколькими важными свойствами, такими как состояние и приоритет. Состояний потока всего три: состояние активности, то есть поток выполняется на данный момент, состояние неактивности, когда поток ожидает выделения процессора для перехода в состояние активности, и третье – состояние блокировки, когда потоку не выделяется время (соответственно он не занимает место в очереди, освобождая ресурсы) вне зависимости от его приоритета.
В C++ для работы с потоками надо подключить файл . После этого простейшая программа с потоками будет выглядеть так:
Источник: proginfo.ru
Процессы, потоки и квартиры
Процесс — это коллекция виртуального пространства памяти, кода, данных и системных ресурсов. Поток — это код, который должен быть последовательно выполнен в процессе. Процессор выполняет потоки, а не процессы, поэтому каждое приложение имеет по крайней мере один процесс, а процесс всегда имеет по крайней мере один поток выполнения, известный как основной поток. В дополнение к основному потоку процесс может содержать несколько потоков.
Урок 33. Процессы и потоки в операционной системе
Процессы взаимодействуют друг с другом через сообщения, используя технологию удаленного вызова процедур (RPC) корпорации Майкрософт для передачи информации друг другу. Вызывающий объект не имеет никакой разницы между вызовом, исходящим из процесса на удаленном компьютере, и вызовом, исходящим из другого процесса на том же компьютере.
Когда поток начинает выполняться, он продолжается до тех пор, пока он не будет завершен или пока не будет прерван потоком с более высоким приоритетом (действием пользователя или планировщиком потоков ядра). Каждый поток может выполнять отдельные разделы кода или несколько потоков могут выполнять один и тот же раздел кода. Потоки, выполняющие один и тот же блок кода, поддерживают отдельные стеки. Каждый поток в процессе совместно использует глобальные переменные и ресурсы этого процесса.
Планировщик потоков определяет, когда и как часто следует выполнять поток, в соответствии с сочетанием атрибута класса priority процесса и базового приоритета потока. Атрибут класса priority процесса задается путем вызова функции SetPriorityClass и базового приоритета потока с помощью вызова Метода SetThreadPriority.
Многопоточные приложения должны избегать двух проблем с потоком: взаимоблокировок и рас. Взаимоблокировка возникает, когда каждый поток ожидает выполнения других действий. Элемент управления вызовами COM помогает предотвратить взаимоблокировки в вызовах между объектами. Состояние гонки возникает, когда один поток завершается перед другим, от которого он зависит, в результате чего первый использует неинициализированное значение, так как последний еще не предоставил допустимое значение. COM предоставляет некоторые функции, специально разработанные для предотвращения состояния гонки на серверах вне процесса. (См. раздел Вспомогательные средства реализации внепроцессного сервера.)
Архитектура подразделения и com-потоков
Хотя COM поддерживает модель с одним потоком на процесс, распространенную до появления нескольких потоков выполнения, вы можете написать код, чтобы воспользоваться преимуществами нескольких потоков, что приводит к более эффективным приложениям, позволяя выполнять один поток, а другой поток ожидает завершения некоторой длительной операции.
Использование нескольких потоков не гарантирует лучшую производительность. На самом деле, так как факторинг потоков является сложной проблемой, использование нескольких потоков часто приводит к проблемам с производительностью. Ключ заключается в том, чтобы использовать несколько потоков только в том случае, если вы уверены в том, что делаете.
Как правило, самый простой способ просмотреть архитектуру потоков COM — подумать, что все COM-объекты в процессе разделены на группы, называемые квартирами. Объект COM находится ровно в одной квартире, в том смысле, что его методы могут быть юридически напрямую вызваны только потоком, принадлежащим этой квартире. Любой другой поток, который хочет вызвать объект, должен проходить через прокси-сервер.
- Однопотоковые квартиры состоят только из одного потока, поэтому все COM-объекты, которые находятся в однопотоковом объекте, могут принимать вызовы методов только из одного потока, который принадлежит к этой квартире. Все вызовы методов к COM-объекту в однопотоковом объекте синхронизируются с очередью сообщений Windows для потока однопотокового подразделения. Процесс с одним потоком выполнения — это просто особый случай этой модели.
- Многопоточные квартиры состоят из одного или нескольких потоков, поэтому все COM-объекты, которые находятся в многопоточном помещении, могут принимать вызовы методов непосредственно из любого из потоков, принадлежащих многопоточному объекту. Потоки в многопоточных подразделениях используют модель, называемую свободными потоками. Вызовы COM-объектов в многопоточной квартире синхронизируются самими объектами.
Описание обмена данными между однопоточными и многопоточными квартирами в рамках одного процесса см. в разделе Однопотоковое и многопоточное взаимодействие.
Процесс может иметь ноль или несколько однопоточных квартир и ноль или одно многопоточное подразделение.
В процессе первой инициализируется main квартира. В однопотоковом процессе это единственная квартира. Параметры вызова маршалируются между квартирами, а COM обрабатывает синхронизацию с помощью обмена сообщениями.
Если вы назначите несколько потоков в процессе как свободные, все свободные потоки находятся в одном экземпляре, параметры передаются непосредственно в любой поток в этом объекте, и необходимо обрабатывать всю синхронизацию. В процессе как со свободными потоками, так и с многопоточными потоками, все свободные потоки находятся в одной квартире, а все остальные квартиры являются однопоточными. Процесс, который выполняет com-работу , представляет собой коллекцию квартир с не более чем одной многопоточной квартирой, но любым количеством однопоточных квартир.
Модели потоков в COM предоставляют механизм для совместной работы клиентов и серверов, использующих различные архитектуры потоков. Вызовы между объектами с разными моделями потоков в разных процессах естественным образом поддерживаются. С точки зрения вызывающего объекта все вызовы объектов за пределами процесса ведут себя одинаково, независимо от того, как вызывается объект, является потоком. Аналогичным образом с точки зрения вызываемого объекта поступающие вызовы ведут себя одинаково, независимо от модели потоков вызывающего объекта.
Взаимодействие между клиентом и внепроцессным объектом является простым, даже если они используют разные потоковые модели, так как клиент и объект находятся в разных процессах. COM, взаимодействующий между клиентом и сервером, может предоставить код для взаимодействия потоковых моделей, используя стандартную маршалинг и RPC. Например, если однопоточный объект вызывается одновременно несколькими клиентами со свободными потоками, вызовы будут синхронизированы com путем размещения соответствующих оконных сообщений в очереди сообщений сервера. Объект будет получать по одному вызову каждый раз при извлечении и отправке сообщений. Однако необходимо соблюдать определенную осторожность, чтобы обеспечить правильное взаимодействие внутрипроцессных серверов с клиентами. (См. раздел Проблемы с потоком внутрипроцессного сервера.)
Самая важная проблема при программировании с многопоточной моделью — сделать код потокобезопасными, чтобы сообщения, предназначенные для определенного потока, отправились только в этот поток и доступ к потокам был защищен.
Дополнительные сведения см. в следующих разделах:
- Выбор потоковой модели
- Однопотоковые квартиры
- Многопоточные квартиры
- Однопотоковый и многопоточный обмен данными
- Проблемы с потоком внутрипроцессного сервера
- Доступ к интерфейсам между квартирами
Источник: learn.microsoft.com
Введение в многопоточность в Java очень простым языком: Процессы, Потоки и Основы синхронизации
На старте вашей карьеры вы вполне можете обойтись без практических навыков в параллельном программировании, но рано или поздно перед вами встанет задача, требующая от вас таких навыков.
Итак, в данной статье мы поговорим о многопоточности в Java. Тема очень обширная, и я не ставлю целью описать все ее аспекты. Статья рассчитана на людей, только начинающих свое знакомство с многопоточностью. В данной статье мы рассмотрим основу многопоточности Java, такие базовые механизмы синхронизации как ключевые слова volatile и synchronized и очень важную проблематику “Состояние гонки” и “Взаимная блокировка”.
Я выбрал немного необычный подход, связав технические примеры с нашей повседневной жизнью, надеюсь вам понравится. Тема будет раскрыта на примере абстрактной комнаты и людей в находящихся в ней.
Дабы максимально упростить материал, я намеренно буду опускать некоторые нюансы реализации и иерархии многопоточности в Java, усложняющие понимание темы. Если вы рассчитываете на подробный обзор с техническими терминами и формулировками, то данная статья вам не подойдет.
Что такое процессы и потоки
Прежде чем перейти к многопоточности, давайте разберемся, что такое процессы и потоки.
Процесс — это экземпляр выполняющейся программы, простыми словами при запуске любой программы на вашем компьютере, вы порождаете процесс. Он имеет свое собственное адресное пространство памяти и один или несколько потоков.
Поток — это последовательность инструкций, выполняющаяся внутри процесса. Потоки делят адресное пространство памяти процесса, что позволяет им работать параллельно.
Создание и управление потоками
Каждый раз когда вы запускаете вашу программу, т.е. порождаете процесс, JVM (виртуальная машина) создает для вас так называемы главный поток (main thread) в котором ваш код будет исполняться.
Из своего главного потока вы можете создавать множество других потоков которые будут исполняться параллельно вашему главному потоку.
В Java создание и управление потоками осуществляется с использованием класса Thread. Чтобы создать поток, необходимо унаследоваться от класса Thread и переопределить его метод run(), в котором указывается код, который будет выполняться в потоке. Затем создается экземпляр класса Thread и вызывается метод start(), чтобы запустить поток.
class MyThread extends Thread < public void run() < System.out.println(«Этот код выполняется в потоке»); >> public class Main < public static void main(String[] args) < MyThread thread = new MyThread(); thread.start(); System.out.println(«Этот код выполняется в главном потоке»); >>
Чтобы объяснить более простым языком что такое потоки и как они взаимодействуют, давайте абстрагируемся от кода и представим что ваша программа (процесс) — это комната, а потоки — это люди, находящиеся в этой комнате. В комнате есть различные предметы (объекты), с которыми люди могут взаимодействовать. Когда вы пускаете несколько людей в комнату (создаете новые потоки), они получают доступ к тем же самым предметам.
Однако, когда несколько людей одновременно пытаются взаимодействовать с одним и тем же предметом, могут возникать конфликты, такие как “Состояние гонки” (Race condition) и “Взаимная блокировка” (Deadlocks).
Состояние гонки
Состояние гонки (Race condition) — это ситуация, когда два или более потока одновременно обращаются к общим данным или ресурсам, и результаты их операций зависят от того, в каком порядке выполняются операции. Это может привести к непредсказуемым и нежелательным результатам, таким как неправильные значения или ошибки в программе. В результате состояния гонки данные или ресурсы могут быть повреждены или использованы неправильно.
Давайте рассмотрим “состояние гонки” в рамках нашего абстрактного примера. Представьте что в комнате стоит стол, а на нем полный стакан воды. В этой же комнате находятся два человека, скажем Саша и Петя. И вот Саша решил сделать глоток воды из этого стакана, чуть позже еще глоток, а потом и еще.
В реальной комнате это выглядело бы примерно так: Саша каждый раз подходил бы к этому стакану, брал бы его, делал глоток и клал бы на место, а потом и сам возвращался на место. Но компьютер это не комната, в нем есть всякие механизмы оптимизации. К примеру, вместо того, чтобы заставлять Сашу ходить туда-сюда каждый раз когда он захотел сделать глоток воды, он создаст копию стакана для Саши когда тот придет за стаканом в первый раз.
Итак, Саша уже имеет свой стакан (копию того стакана, что остался стоять на столе), и ему уже не надо каждый раз ходить за стаканом, он просто сидит на своем месте и делает три глотка воды. Но, несмотря на то что у Саши копия стакана, а оригинал остался стоять на столе, он знает что должен его отнести на место и заменить своей копией оригинал, это процесс называется синхронизация и необходимо для поддержания актуального состояния уровня воды в стакане (значения переменной).
В то время как Саша пил из своей копии стакана, Петя тоже захотел глотнуть воды для чего подошел к столу где стоит все еще полный стакан воды потому как Петя еще не вернул свой стакан (синхронизация еще не произошла) и соответственно получил свою копию полного стакана, после чего вернулся на место и сделал два глотка. В то время как Петя пил из своей копии стакана, Саша уже закончил пить и отнес свой стакан на место и соответственно заменил им оригинальный стакан (совершил синхронизацию), в итоге количество воды в стакане на столе уменьшилось на 3 глотка. Через какое-то время Петя тоже закончил пить и отнес свой стакан на место и соответственно заменил им стакан, стоящий на столе. В итоге объем воды в стакане на столе — это полный стакан минус два глотка Пети, а глотки Саши утеряны. Это и есть состояние гонки и результаты ее могут быть абсолютно не предсказуемыми.
Для борьбы с этим явлением Java предоставляет различные механизмы, базовым является использование ключевого слова volatile.
Ключевое слово volatile используется для обозначения переменных, которые могут быть изменены несколькими потоками. Оно гарантирует, что изменения переменной видны другим потокам.
Проще говоря, если вернуться к примеру с Сашей и Петей, то каждый раз, когда кто-нибудь из них захочет глотнуть воды, то он будет подходить к стакану брать его и после глотка сразу же ставить на место, да в этом случае уже нет оптимизации, но зато объем воды в стакане всегда актуальный и это избавляет нас от состояния гонки.
Ниже приведен пример кода, при помощи которого вы можете наблюдать результаты “состояние гонки”. При каждом запуске данного кода результаты будут непредсказуемы, хотя ожидалось увидеть цифру 2000. Проблема легко решается добавлением ключевого слова volatile в декларации переменной private volatile int volume = 0;
class Scratch < static class GlassOfWater < private int volume = 0; public int getVolume() < return volume; >public void setVolume(int volume) < this.volume = volume; >> public static void main(String[] args) throws InterruptedException < GlassOfWater glassOfWater = new GlassOfWater(); Thread thread1 = new Thread(() -> < for (int i = 0; i < 1000; i++) < glassOfWater.setVolume(glassOfWater.getVolume() + 1); >>); Thread thread2 = new Thread(() -> < for (int i = 0; i < 1000; i++) < glassOfWater.setVolume(glassOfWater.getVolume() + 1); >>); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(«Volume is » + glassOfWater.getVolume()); > >
Взаимная блокировка
Взаимная блокировка (Deadlock) — это ситуация, когда два или более потока зацикленно ожидают друг друга, чтобы освободить ресурсы или завершить определенные операции. В результате ни один из потоков не может продолжить свое выполнение, так как каждый из них блокирует ресурсы, необходимые для завершения работы другого потока. Это приводит к тому, что программа останавливается и не может продолжить свое выполнение, пока ситуация deadlock не будет разрешена вручную.
Вернемся в ту же комнату, где нас ожидают Саша и Петя, и представим что в этой же комнате есть два чемоданчика, первый с различными инструментами типа плоскогубцы, молоток, всякие ключи и т.д, а во втором у нас крепеж, ну типа гайки, шурупы, гвозди, саморезы и т.д. Саша решил что-то отремонтировать в комнате и ему понадобились оба эти чемоданчика. И в то же время он хочет быть уверенным что никто ничего от туда не возьмет пока он работает, для этого он запереть на ключи нужные ему чемоданчики. Таким образом каждый раз когда он что-то будет брать или класть в чемоданчик он его отпирает и запирает.
Данный прием в Java называется блокировкой, это как ключ, который только один человек может держать в руках в определенный момент времени. Когда кто-то владеет ключом (блокирует доступ), другие люди должны ждать своей очереди. Блокировка гарантирует, что критическая секция кода будет выполняться только одним потоком за раз. Таким ключиком в языке Java является ключевое слово synchronized.
Ключевое слово synchronized обеспечивает атомарность операций и предотвращает конфликты между потоками. Это слово может быть применено как к методу целиком, так и к отдельному участку кода.
class MyClass < int counter; public synchronized void doSomething() < counter++; >> class MyClass < int counter; public void doSomething() < synchronized(this) < counter++; >> >
С понятием synchronized мы разобрались и можем продолжить обзор того что же такое “Взаимная блокировка”.
Вернемся к Саше, сначала он взял ключ от ящика с инструментами и запер его, но в это же время Петя тоже решил заняться ремонтом и так же решил запереть чемоданчик, но первым он запер чемоданчик с крепежом. Далее Пете нужен чемоданчик с инструментами, он пошел к этому чемоданчику и видит что он уже заперт Сашей, “ну что же раз такое дело то подожду пока Саша завершит работу с ним” подумал Петя. В то же время Саше нужен чемоданчик с крепежом, но он так же видит что чемоданчик уже заперт Петей, в итоге Саша так же решил подождать. Мораль в том что они будут ждать до бесконечности, так и возникает эта самая взаимная блокировка.