Потоки ч.2

Приветствую всех, сегодня поговорим о продолжении темы связанной с потоками, это часть 2.  Читать Потоки ч.1

Различают две разновидности многозадачности: на основе процессов и на основе потоков. В связи с этим важно понимать отличия между ними.

Процесс фактически представляет собой исполняемую программу. Поэтому многозадачность на основе процессов — это средство, благодаря которому на
компьютере могут параллельно выполняться две программы и более. Поток представляет собой координируемую единицу исполняемого кода. Своим происхождением этот термин обязан понятию «поток исполнения». При организации многозадачности на основе потоков у каждого процесса должен быть по крайней мере один поток, хотя их может быть и больше. Это означает, что в одной программе одновременно могут решаться две задачи и больше.

Отличия в многозадачности на основе процессов и потоков могут быть сведены к следующему: многозадачность на основе процессов организуется для параллельного выполнения программ, а многозадачность на основе потоков — для параллельного выполнения отдельных частей одной программы.

Поток может находиться в одном из нескольких состояний:

  • В целом, поток может быть выполняющимся;
  • Готовым к выполнению, как только он получит время и ресурсы ЦП;
  • Приостановленным, т.е. временно не выполняющимся; возобновленным в дальнейшем;
  • Заблокированным в ожидании ресурсов для своего выполнения; а также завершенным, когда его выполнение окончено и не может быть возобновлено.

В среде .NET Framework определены две разновидности потоков: приоритетный и фоновый. По умолчанию создаваемый поток автоматически становится приоритетным,
но его можно сделать фоновым. Единственное отличие приоритетных потоков от фоновых заключается в том, что фоновый поток автоматически завершается, если в его процессе остановлены все приоритетные потоки.

System.Threading — пространство имен для работы с потоками, содержит классы для управления потоками, такие как: Thread, Monitor, Interlocked.

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

Для определения момента окончания потока можно воспользоваться значением свойства IsAlive. Оно вернет false, когда поток завершится. Кроме того, можно воспользоваться специальным методов Join(), который позволяет приостановить выполнение основного потока до момента завершения тех вторичных потоков, для которых метод Join() был использован.

Пример работы с Join():

Если за комментировать строчку task1.Join() Мы уведем абсолютно другое, хаус:

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

Для вызова метода без аргументов в обычном потоке используется делегат ThreadStart, а для передачи аргументов в поток, необходимо использовать делегат ParameterizedThreadStart. С его помощью в поток можно передать один аргумент типа object.

Для того чтобы сделать поток фоновым, достаточно присвоить логическое значение true свойству IsBackground. А логическое значение false указывает на то, что поток
является приоритетным.

Пример IsBackground:

У каждого потока имеется свой приоритет, который отчасти определяет, насколько часто поток получает доступ к ЦП. Вообще говоря, низкоприоритетные потоки получают доступ к ЦП реже, чем высокоприоритетные. Следует иметь в виду, что, помимо приоритета, на частоту доступа потока к ЦП оказывают влияние и другие факторы. Так, если высокоприоритетный поток ожидает доступа к некоторому ресурсу, например для ввода с клавиатуры, он блокируется, а вместо него выполняется низкоприоритетный поток. В подобной ситуации низкоприоритетный поток может получать доступ к ЦП чаще, чем высокоприоритетный поток в течение определенного периода времени. И наконец, конкретное планирование задач на уровне операционной системы также оказывает влияние на время ЦП, выделяемое для потока.

Приоритет потока можно изменить с помощью свойства Priority, являющегося членом класса Thread. По умолчанию для потока устанавливается значение приоритета ThreadPriority.Normal.

Пример работы с приоритетами:

Многопоточный код может вести себя по-разному в различных средах, поэтому никогда не следует полагаться на результаты его выполнения только в одной среде. Было бы ошибкой полагать, что низкоприоритетный поток из приведенного выше примера будет всегда выполняться лишь в течение небольшого периода времени до тех пор,
пока не завершится высокоприоритетный поток. В другой среде высокоприоритетный поток может, например, завершиться еще до того, как низкоприоритетный поток выполнится хотя бы один раз.

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

В основу синхронизации положено понятие блокировки, посредством которой организуется управление доступом к кодовому блоку в объекте. Когда объект заблокирован одним потоком, остальные потоки не могут получить доступ к заблокированному кодовому блоку. Когда же блокировка снимается одним потоком, объект становится доступным для использования в другом потоке.

Итоги использования блокировки:

  • Если блокировка любого заданного объекта получена в одном потоке, то после блокировки объекта она не может быть получена в другом потоке.
  • Остальным потокам, пытающимся получить блокировку того же самого объекта, придется ждать до тех пор, пока объект не окажется в разблокированном состоянии.
  • Когда поток выходит из заблокированного фрагмента кода, соответствующий объект разблокируется.

Класс Monitor представляет собой механизм для синхронизации доступа к объектам. Класс Monitor используется для создания критических секций.

Синхронизация организуется с помощью ключевого слова lock.

Ключевое слово lock на самом деле служит в С# быстрым способом доступа к средствам синхронизации, определенным в классе Monitor, который находится в пространстве имен System.Threading. В этом классе определен, в частности, ряд методов для управления синхронизацией. Например, для получения блокировки объекта вызывается метод Enter(), а для снятия блокировки — метод Exit().

Пример использования lock:

Методы Wait(), Pulse() и PulseAll() определены в классе Monitor и могут вызываться только из заблокированного фрагмента блока. Они применяются следующим образом.
Когда выполнение потока временно заблокировано, он вызывает метод Wait (). В итоге поток переходит в состояние ожидания, а блокировка с соответствующего объекта снимается, что дает возможность использовать этот объект в другом потоке. В дальнейшем ожидающий поток активизируется, когда другой поток войдет в аналогичное состояние блокировки, и вызывает метод Pulse() или PulseAll(). При вызове метода Pulse() возобновляется выполнение первого потока, ожидающего своей очереди на получение блокировки. А вызов метода PulseAll() сигнализирует о снятии блокировки всем ожидающим потокам.

Наиболее опасными проблемами, из тех, что возникают во время параллельного выполнения нескольких потоков являются проблемы взаимоблокировки и состояния гонки. Взаимоблокировка – это такое состояние потоков, когда каждый ждет окончания работы остальных и при этом ничего не делает. Состояние гонки — это попытки потоков
получить доступ к одному ресурсу без должной его блокировки.

Метод Abort(); предоставляет возможность остановки потока из другого потока, путем создания исключения ThreadAbortException

Рассмотрим пример работы метода Abort();

В примере мы в основном потоке даем вторичному потоке поработать 2 секунды и вызываем исключения, тем самым делаем прерывания второго потока, и оба потока завершаются.

Однако есть метод Thread.ResetAbort(); который позволяет нам отменить действия запущенные методом Abort() и возобновить наш поток:

Цель этого метода позволить продолжить работу программы после исключения Thread.ResetAbort(); в блоке cath. Так как у нас генерируется повторно исключения внутри блока catch и не дает нам выйти за его приделы, но мы этого не видим.

 

Иногда в потоках необходимо использовать общую переменную сделать это можно с помощью класса Interlocked и его методов, покажу один из примеров как использовать общую long в двух потоках, где происходит ее итерация:

 

Читать Потоки ч.3

 

Обновлено: 05.07.2018 — 14:38

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.