Опубликован: 05.03.2013 | Доступ: свободный | Студентов: 2195 / 622 | Длительность: 07:41:00
Лекция 3:

Работа с потоками

Аннотация: Создание потоков, ожидания завершения потоков. Пул потоков ThreadPool.

В системах с общей памятью, включая многоядерные архитектуры, параллельные вычисления могут выполняться как при многопроцессном выполнении, так и при многопоточном выполнении. Многопроцессное выполнение подразумевает оформление каждой подзадачи в виде отдельной программы (процесса). Недостатком такого подхода является сложность взаимодействия подзадач. Каждый процесс функционирует в своем виртуальном адресном пространстве, не пересекающемся с адресным пространством другого процесса. Для взаимодействия подзадач необходимо использовать специальные средства межпроцессной коммуникации (интерфейсы передачи сообщений, общие файлы, объекты ядра операционной системы).

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

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

Структура потока

Поток состоит из нескольких структур. Ядро потока – содержит информацию о текущем состоянии потока: приоритет потока, программный и стековый указатели. Программный и стековые указатели образуют контекст потока и позволяют восстановить выполнение потока на процессоре. Блок окружения потока - содержит заголовок цепочки обработки исключений, локальное хранилище данных для потока и некоторые структуры данных, используемые интерфейсом графических устройств (GDI) и графикой OpenGL. Стек пользовательского режима – используется для передаваемых в методы локальных переменных и аргументов. Стек режима ядра - используется, когда код приложения передает аргументы в функцию операционной системы, находящуюся в режиме ядра. Ядро ОС вызывает собственные методы и использует стек режима ядра для передачи локальных аргументов, а также для сохранения локальных переменных.

Состояния потоков

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


Переключение контекста

  1. Значения регистров процессора для исполняющегося в данный момент потока сохраняются в структуре контекста, которая располагается в ядре потока.
  2. Из набора имеющихся потоков выделяется тот, которому будет передано управление. Если выбранный поток принадлежит другому процессу, Windows переключает для процессора виртуальное адресное пространство.
  3. Значения из выбранной структуры контекста потока загружаются в регистры процессора.

Работа с потоками в 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
    

Маловероятны, но возможны варианты

    ААВВАААВССССС
    

или

    ААААВВВВВАССССС