Многопоточное программирование ( часть 1) Лекция 10
Процесс – выполняющаяся программа ( экземпляр ) Процесс может содержать один или несколько потоков Поток ( нить, thread) – путь выполнения внутри исполняемого приложения При запуске приложения создается и запускается главный поток Любой поток может запускать дополнительные потоки Потоки выполняются параллельно и независимо Завершение процесса – завершение всех его потоков Нет четкой корреляции между потоком операционной системы и управляемым потоком.NET Один поток ОС может обслуживать несколько потоков.NET Многопоточные приложения могут выполнятся и на однопроцессорном компьютере
При грамотном подходе может значительно ускорить работу приложения ( только при многоядерной или много процессорной архитектуре ) Позволяет повысить отзывчивость пользовательского интерфейса ( даже при однопроцессорной архитектуре ) Позволяет ускорить работу приложения за счет одновременного выполнения : долгих удаленных операций ( выполняющихся на других компьютерах ) Например, запрос к базе данных, к сервису или к интернет ресурсу медленных, но мало затратных операций Например, сохранение или чтение с диска Трудности разработки ( дороговизна разработки ) Разбиение и оптимизация программы для многопоточной работы Синхронизация потоков Тестирование Трудности тестирования и отладки Трудно обнаружимые ошибки Невоспроизводимые ошибки Непредсказуемые ошибки При неграмотном подходе может замедлить приложение На создание и поддержание работы потоков тратятся ресурсы
Пространства имен System.Threading System.Threading.Tasks System.ComponentModel ( поток для UI, BackgroundWorker) Класс System.Threading.Thread Методы для работы с потоками Статические члены для текущего потока static Thread Thread.CurrentThread – текущий поток Единица кода для запуска в потоке – метод В отдельном потоке всегда запускается кокой - то метод
Необходимо создать метод, который будет выполнятся новым потоком public static void threadMethod() {…} Создание экземпляра делегата на метод ThreadStart – для запуска потока без параметров ParameterizedThreadStart – для запуска потока с одним параметром ( но параметр object) Создание потока и передача ему делегата на метод Thread thread= new Thread(new ThreadStart(threadMethod)); Start() Запуск потока thread.Start();
Использование делегата ParameterizedThreadStart вместо ThreadStart Передача только 1 параметра, но параметра типа object public static void threadMethod(object o){..} Thread thread = new Thread(new ParameterizedThreadStart(threadMethod)); thread.Start(obj); Другой способ – класс - обертка
Thread Передача параметров Класс обертка
Свойства потока Name Name – имя потока ( удобно использовать для отладки ) ManagedThreadId ManagedThreadId – уникальный ID потока Priority Priority – приоритет потока IsAlive IsAlive – поток запущен и не приостановлен ThreadState ThreadState – состояние потока IsBackground IsBackground – фоновый ли поток IsThreadPoolThread IsThreadPoolThread – принадлежит ли поток пулу потоков CLR Полезные методы и свойства для работы с потоками CurrentThread Thread.CurrentThread – ссылка на текущий поток ( статическое вычислимое свойство ) Sleep() Thread.Sleep() – заставляет поток ожидать указанное время ( статический метод ) Join() thread.Join() – заставляет ожидать текущий поток завершения указанного потока. Abort() thread.Abort() – заставляет аварийно завершить поток
UnstartedUnstarted RunningRunning SuspendSuspend SuspendRequestedSuspendRequested WaitSleepJoinWaitSleepJoin FinishedFinished Abort Requested
Поток завершится при выходе из метода Abort() thread.Abort() – аварийное завершение потока При этом у прерываемого потока возникает исключение ThreadAbortedException Прерываемый поток может обработать исключение ThreadAbortedException, но после этого исключение будут вызвано снова AbortReset() thread.AbortReset() – отмена прерывания потока ( если успеть, пока поток еще аварийно не завершился ) Join() thread.Join() – блокировка текущего потока до завершения другого потока Завершенный поток нельзя запустить снова
Потоки : Потоки переднего плана ( по умолчанию ) Фоновые потоки Процесс не завершится пока есть работающие потоки переднего плана Фоновые потоки при завершении основного потока получают исключение ThreadAbortedException и будут завершены Необходима реализация безопасного завершения фонового потока Установка потока как фонового IsBackground thread.IsBackground = true;
Завершение фонового потока
В среде выполнения уже существует несколько запущенных потоков – пул потоков Количество поток связано с количеством процессоров. При использовании потока из пула потоков нет накладных расходов на создание потока В пуле потоки фоновые Класс ThreadPool – позволяет получить доступ к пулу потоков.NET Постановка задания в очередь Создание экземпляра делегата void WaitCallback(object state ) ThreadPool.QueueUserWorkItem Постановка в очередь ThreadPool.QueueUserWorkItem WaitCallback (new WaitCallback(threadMethod), obj); Переданное задание уже нельзя отменить
Пул потоков
BeginInvoke()EndInvoke() Любой делегат имеет помимо метода для синхронного вызова – Invoke(), методы для асинхронного вызова BeginInvoke(), EndInvoke() Func f = …. IAsyncResult f.BeginInvoke(string s, double d, AsyncCallback callback, object obj) – начинает вызов и передает параметры string, double int f.EndInvoke(IAsyncResult ires) – ожидает завершения и возвращает значение AsyncCallback callback – делегат будет вызван при окончании вычисления
Свойство bool IsComplated – завершено ли вычисление Свойство object AsyncState – позволяет передавать параметры для последующий идентификации вызванного метода
Асинхронный вызов делегата
Простой запуск выполнения действия в Thread Pool Tack - класс для вызова метода, ничего не возвращающего, а Task для возвращающего результат Tresult Start() Запуск таски – Start(). Конструктор – настройка таски void inc() { … } Task t = new Task(inc); t.Start(); Task t = new Task (GetInt); t.Start(); Task.Factory.StartNew Быстрый старт заданий ( рекомендуемый ) Task.Factory.StartNew() Task t = Task.Factory.StartNew(inc); Task t = Task.Factory.StartNew(GetInt); Task t = Task.Factory.StartNew(Add, new object[] { 5,7 } ); Result Получение результата по окончании таски Task - свойство Result int res = t.Result; // если t – Task
ContinueWith Продолжение выполнения ContinueWith(); Метод будет выполняться по завершении таски ( сама таска пойдет на вход методу ) void inc() { … } void a(Task t) { … } Task task = Task.Factory.StartNew(inc); task.ContinueWith(a); Синхронизация тасок Wait() Ожидание завершения таски Wait(); t.Wait(); Task.WaitAll() Ожидание завершения всех тасок Task.WaitAll() Task.WaitAll(task1, task2, task3) Task.WaitAny() Ожидание завершения хотя бы одной тасок Task.WaitAny() Task.WaitAny(task1, task2, task3) Отмена тасок – передача токена отмены CancellationToken
Parallel.For Parallel.For(initvalue, endvalue, Action ); - Выполнение цикла в максимально возможном числе потоков (ThreadPool). В цикле выполняется делегат Action ( который принимает 1 параметр T и, ничего не возвращает ). Числом потоков управляет CLR Parallel.ForEach Parallel.ForEach (IEnumerable, Action ); - Выполнение делегата Action над всеми элементами перечисления в максимально возможном числе потоков. Числом потоков управляет CLR List l = new List (); public void dec(int i) {} Parallel.For(0, 10, dec); Parallel.ForEach (l, dec); Parallel.Invoke Parallel.Invoke(params Action[] actions) – выполнение делегатов в отдельных потоках, если возможно Parallel.Invoke(Print, PrintToScreen, SendTo , () => Console.WriteLine(" Печатаем ")); ParallelOptions Класс ParallelOptions может использоваться для подстройки операций Parallel MaxDegreeOfParallelism MaxDegreeOfParallelism – ограничивает максимально число одновременно выполняющихся задач в классом Parallel. CancellationToken CancellationToken – позволяет отменять задания, выполняющиеся классом Parallel
Parallel Task
Потоки выполняются параллельно и независимо. Нельзя предсказать очередность выполнения блоков кода потоками. static void Main() { Thread t = new Thread(Write1); t.Start(); while (true) Console.Write("-"); // Все время печатать '-' } static void Write1() { while (true) Console.Write("1"); // Все время печатать '1' }
У каждого потока свой стек локальных переменных. Они независимые. static void Main() { new Thread(Go).Start(); // Выполнить Go() в новом потоке Go(); // Выполнить Go() в главном потоке } static void Go() { // Определяем и используем локальную переменную 'cycles' for (int cycles = 0; cycles < 5; cycles++) Console.Write('+'); }
Вместе с тем потоки разделяют данные, относящиеся к тому же экземпляру объекта class TestClass { bool done = false; public void Go() { if (!done) { done = true; Console.WriteLine("Done"); } } } class ThreadTest { static void Main() { TestClass testClass = new TestClass(); new Thread(testClass.Go).Start(); testClass.Go(); }
class Increment { decimal l = 0; public void inc() { for (int i = 0; i < ; ++i) l = l + 1; Console.WriteLine(l); } } class Program { static void Main(string[] args) { Increment i = new Increment (); for (int j = 0; j < 10; ++j) new Thread(i.inc).Start(); } }
Потоки выполняются параллельно и независимо. Нельзя предсказать какой поток отработает быстрее. У каждого потока свой собственный стек. Собственные неразделяемые локальные переменные Потоки разделяют нелокальные переменные, доступные им по области видимости Операции неатомарные