| Россия |
Работа с потоками
В системах с общей памятью, включая многоядерные архитектуры, параллельные вычисления могут выполняться как при многопроцессном выполнении, так и при многопоточном выполнении. Многопроцессное выполнение подразумевает оформление каждой подзадачи в виде отдельной программы (процесса). Недостатком такого подхода является сложность взаимодействия подзадач. Каждый процесс функционирует в своем виртуальном адресном пространстве, не пересекающемся с адресным пространством другого процесса. Для взаимодействия подзадач необходимо использовать специальные средства межпроцессной коммуникации (интерфейсы передачи сообщений, общие файлы, объекты ядра операционной системы).
Потоки позволяют выделить подзадачи в рамках одного процесса. Все потоки одного приложения работают в рамках одного адресного процесса. Для взаимодействия потоков не нужно применять какие-либо средства коммуникации. Потоки могут непосредственно обращаться к общим переменным, которые изменяют другие потоки. Работа с общими переменными приводит к необходимости использования средств синхронизации, регулирующими порядок работы потоков с данными.
Потоки являются более легковесной структурой по сравнению с процессами ("облегченные" процессы). Поэтому параллельная работа множества потоков, решающих общую задачу, более эффективна в плане временных затрат, чем параллельная работа множества процессов.
Структура потока
Поток состоит из нескольких структур. Ядро потока – содержит информацию о текущем состоянии потока: приоритет потока, программный и стековый указатели. Программный и стековые указатели образуют контекст потока и позволяют восстановить выполнение потока на процессоре. Блок окружения потока - содержит заголовок цепочки обработки исключений, локальное хранилище данных для потока и некоторые структуры данных, используемые интерфейсом графических устройств (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
Маловероятны, но возможны варианты
ААВВАААВССССС
или
ААААВВВВВАССССС
