Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Критические секции






Рассмотрим следующий класс:

public class ThreadUnsafe

{

private static int x, y;

 

public void Go()

{

if (y! = 0)

{

Console.WriteLine(x/y);

}

y = 0;

}

}

Этот класс небезопасен с точки зрения многопоточного доступа к данным. Если вызвать метод Go() в разных потоках одновременно, может возникнуть ошибка деления на ноль, так как поле y будет обнулено в одном потоке как раз между проверкой условия y! = 0 и вызовом Console.WriteLine() в другом потоке. Чтобы сделать код потокобезопасным, необходимо гарантировать выполнение операторов, составляющих тело метода Go(), только одним потоком в любой момент времени. Такие блоки кода называются критическими секциями.

Для организации критических секций платформа.NET предлагает статический класс System.Threading.Monitor. Метод Monitor.Enter() определяет вход в критическую секцию, а метод Monitor.Exit() – выход из секции. Вход и выход должны выполняться в одном и том же потоке. Аргументами методов является объект-идентификатор критической секции.

Изменим метод Go(), чтобы сделать его потокобезопасным:

public class ThreadSafe

{

// объект locker будет идентификатором критической секции

private static readonly object locker = new object();

private static int x, y;

 

public void Go()

{

Monitor.Enter(locker); // вход в критическую секцию

if (y! = 0)

{

Console.WriteLine(x/y);

}

y = 0;

Monitor.Exit(locker); // выход из критической секции

}

}

Рис. 16 демонстрирует поведение двух потоков, работающих с одной критической секцией.

Рис. 16. Два потока работают с одной критической секцией.

Метод Monitor.Enter() имеет перегруженную версию, с ref-параметром типа bool. Если вход в критическую секцию был выполнен успешно, этот параметр возвращается как true. Если организовать критическую секцию не удалось (например, по причине недостатка памяти), параметр возвращается как false, а метод Enter() выбрасывает исключение.

Язык C# содержит оператор lock, маскирующий вызовы методов Monitor.Enter() и Monitor.Exit(). Синтаксис оператора lock следующий:

lock (выражение) вложенный-оператор

Здесь выражение должно иметь ссылочный тип и задаёт идентификатор критической секции. Часто в качестве выражения записывают поле или переменную, на которую накладывается блокировка. Оператор lock вида lock (x) эквивалентен следующему коду:

bool lockWasTaken = false;

try

{

Monitor.Enter(x, ref lockWasTaken);

// здесь размещается вложенный оператор lock

}

finally

{

if (lockWasTaken)

{

Monitor.Exit(x);

}

}

Перепишем метод Go() класса ThreadSafe, используя оператор lock:

public void Go()

{

lock (locker)

{

if (y! = 0)

{

Console.WriteLine(x/y);

}

y = 0;

}

}

Класс System.Threading.Mutex (мьютекс) подобен классу Monitor, но позволяет организовать критическую секцию для нескольких процессов. Применяя Mutex, нужно вызвать метод WaitOne() для входа в критическую секцию, а метод ReleaseMutex() – для выхода из неё (выход может быть произведён только в том же потоке выполнения, что и вход).

Типичный пример использования мьютекса – создание приложения, которое можно запустить только в одном экземпляре:

 

using System;

using System.Threading;

 

public class OneAtATimePlease

{

public static void Main()

{

// имя мьютекса должно быть уникально для компьютера

using (var mutex = new Mutex(false, " RandomString"))

{

// пытаемся войти в критическую секцию в течение 3 сек.

// ожидаем 3 секунды на случай, если другой экземпляр

// приложения в процессе завершения работы

if (! mutex.WaitOne(TimeSpan.FromSeconds(3), false))

{

Console.WriteLine(" Another instance is running");

return;

}

RunProgram();

}

}

 

private static void RunProgram()

{

Console.WriteLine(" Running (press Enter to exit)");

Console.ReadLine();

}

}

Семафор – это объект синхронизации, позволяющий войти в заданный участок кода не более чем N потокам (N – ёмкость семафора). Аналогией семафора является охранник у входа в клуб с фиксированным количеством мест. Новые посетители попадают в заполненный клуб, только если из него кто-то ушёл. Семафор с ёмкостью, равной единице, аналогичен монитору или мьютексу, однако получение и снятие блокировки в случае семафора может выполняться из разных потоков.

Для организации семафоров платформа.NET предлагает классы Semaphore и SemaphoreSlim из пространства имён System.Threading. Первый класс применяется для синхронизации между процессами, второй работает только в рамках одного процесса. Метод Wait() этих классов выполняет получение блокировки, а метод Release() – снятие блокировки.

using System;

using System.Threading;

 

public class TheClub

{

// ёмкость семафора равна 2

private static SemaphoreSlim s = new SemaphoreSlim(2);

 

public static void Main()

{

for (var i = 1; i < = 4; i++)

{

new Thread(Enter).Start(i);

}

}

 

private static void Enter(object id)

{

Console.WriteLine(id + " wants to enter");

s.Wait();

 

Console.WriteLine(id + " is in! "); // только два потока

Thread.Sleep(1000 * (int) id); // могут одновременно

Console.WriteLine(id + " is leaving"); // выполнять этот код

 

s.Release();

}

}

Рис. 17. Демонстрация работы семафора в классе TheClub.

Иногда ресурс нужно блокировать так, чтобы читать его могли несколько потоков, а записывать – только один. Для этих целей предназначен класс ReaderWriterLockSlim. Его экземплярные методы EnterReadLock() и ExitReadLock() задают секцию чтения ресурса, а методы EnterWriteLock() и ExitWriteLock() – секцию записи ресурса. В следующем примере при помощи ReaderWriterLockSlim построен класс, реализующий простой кэш с поддержкой многопоточности:

 

public class SynchronizedCache

{

private Dictionary< int, string> cache =

new Dictionary< int, string> ();

private ReaderWriterLockSlim locker =

new ReaderWriterLockSlim();

 

public string Read(int key)

{

locker.EnterReadLock(); // секция чтения началась

try

{

return cache[key]; // читать могут несколько потоков

}

finally

{

locker.ExitReadLock(); // секция чтения закончилась

}

}

 

public void Add(int key, string value)

{

locker.EnterWriteLock(); // секция записи началась

try

{

cache.Add(key, value);

}

finally

{

locker.ExitWriteLock(); // секция запись закончилась

}

}

 

public bool AddWithTimeout(int key, string value, int timeout)

{

if (locker.TryEnterWriteLock(timeout)) // таймаут входа

{

try

{

cache.Add(key, value);

}

finally

{

locker.ExitWriteLock();

}

return true;

}

return false;

}

}






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.