Работа с потоками
В системах с общей памятью, включая многоядерные архитектуры, параллельные вычисления могут выполняться как при многопроцессном выполнении, так и при многопоточном выполнении. Многопроцессное выполнение подразумевает оформление каждой подзадачи в виде отдельной программы (процесса). Недостатком такого подхода является сложность взаимодействия подзадач. Каждый процесс функционирует в своем виртуальном адресном пространстве, не пересекающемся с адресным пространством другого процесса. Для взаимодействия подзадач необходимо использовать специальные средства межпроцессной коммуникации (интерфейсы передачи сообщений, общие файлы, объекты ядра операционной системы).
Потоки позволяют выделить подзадачи в рамках одного процесса. Все потоки одного приложения работают в рамках одного адресного процесса. Для взаимодействия потоков не нужно применять какие-либо средства коммуникации. Потоки могут непосредственно обращаться к общим переменным, которые изменяют другие потоки. Работа с общими переменными приводит к необходимости использования средств синхронизации, регулирующими порядок работы потоков с данными.
Потоки являются более легковесной структурой по сравнению с процессами ("облегченные" процессы). Поэтому параллельная работа множества потоков, решающих общую задачу, более эффективна в плане временных затрат, чем параллельная работа множества процессов.
Структура потока
Поток состоит из нескольких структур. Ядро потока – содержит информацию о текущем состоянии потока: приоритет потока, программный и стековый указатели. Программный и стековые указатели образуют контекст потока и позволяют восстановить выполнение потока на процессоре. Блок окружения потока - содержит заголовок цепочки обработки исключений, локальное хранилище данных для потока и некоторые структуры данных, используемые интерфейсом графических устройств (GDI) и графикой OpenGL. Стек пользовательского режима – используется для передаваемых в методы локальных переменных и аргументов. Стек режима ядра - используется, когда код приложения передает аргументы в функцию операционной системы, находящуюся в режиме ядра. Ядро ОС вызывает собственные методы и использует стек режима ядра для передачи локальных аргументов, а также для сохранения локальных переменных.
Состояния потоков
Каждый поток может находиться в одном из нескольких состояний. Поток, готовый к выполнению и ожидающий предоставления доступа к центральному процессору, находится в состоянии "Готовый". Поток, который выполняется в текущий момент времени, имеет статус "Выполняющийся". При выполнении операций ввода-вывода или обращений к функциям ядра операционной системы, поток снимается с процессора и находится в состоянии "Ожидает". При завершении операций ввода-вывода или возврате из функций ядра поток помещается в очередь готовых потоков. При переключении контекста поток выгружается и помещается в очередь готовых потоков.
Переключение контекста
- Значения регистров процессора для исполняющегося в данный момент потока сохраняются в структуре контекста, которая располагается в ядре потока.
- Из набора имеющихся потоков выделяется тот, которому будет передано управление. Если выбранный поток принадлежит другому процессу, Windows переключает для процессора виртуальное адресное пространство.
- Значения из выбранной структуры контекста потока загружаются в регистры процессора.
Работа с потоками в C#
Среда исполнения .NET CLR предоставляет возможность работы с управляемыми потоками через объект Thread пространства имен System.Threading. Среда исполнения стремится оптимизировать работу управляемых потоков и использовать для их выполнения потоки процесса, существующие на уровне операционной системы. Поэтому создание потоков типа Thread не всегда сопряжено с созданием потоков процесса.
Основные этапы работы
// Инициализация потока Thread oneThread = new Thread(Run); // Запуск потока oneThread.Start(); // Ожидание завершения потока oneThread.Join();
В качестве рабочего элемента можно использовать метод класса, делегат метода или лямбда-выражение. В следующем фрагменте создаются три потока. Первый поток в качестве рабочего элемента принимает статический метод LocalWorkItem. Второй поток инициализируется с помощью лямбда-выражения, третий поток связывается с методом общедоступного класса.
class Program { static void LocalWorkItem() { Console.WriteLine("Hello from static method"); } static void Main() { Thread thr1 = new Thread(LocalWorkItem); thr1.Start(); Thread thr2 = new Thread(() => { Console.WriteLine("Hello from lambda-expression"); }); thr2.Start(); ThreadClass thrClass = new ThreadClass("Hello from thread-class"); Thread thr3 = new Thread(thrClass.Run); thr3.Start(); } } class ThreadClass { private string greeting; public ThreadClass(string sGreeting) { greeting = sGreeting; } public void Run() { Console.WriteLine(greeting); } }
Вызов метода thr1.Join() блокирует основной поток до завершения работы потока thr1.
Thread thr1 = new Thread(() => { for(int i=0; i<5; i++) Console.Write("A"); }); Thread thr2 = new Thread(() => { for(int i=0; i<5; i++) Console.Write("B"); }); Thread thr3 = new Thread(() => { for(int i=0; i<5; i++) Console.Write("C"); }); thr1.Start(); thr2.Start(); thr1.Join(); thr2.Join(); thr3.Start();
В общем случае порядок вывода первого и второго потоков не определен. Вывод третьего потока осуществляется только после завершения работы потоков thr1 и thr2.
Можно получить такие результаты:
AAAAABBBBBCCCCC
или
BBBBBAAAAACCCCC
Маловероятны, но возможны варианты
ААВВАААВССССС
или
ААААВВВВВАССССС