Классы синхронизации в .NET Framework
Синхронизация потоков
Оператор lock
Оператор lock предназначен для того, чтобы одному потоку не дать войти в важный раздел кода в тот момент, когда в нем находится другой поток. При попытке входа другого потока в заблокированный код потребуется дождаться снятия блокировки объекта. Этот оператор оформляется следующим образом:
Object thisLock = new Object();
lock (thisLock)
{
// Критический фрагмент код
}
где thisLock - обозначает ссылку на синхронизируемый объект, который гарантирует, что фрагмент кода, защищенный блокировкой для данного объекта, будет использоваться только в потоке, получающем эту блокировку, а все остальные потоки блокируются до тех пор, пока блокировка не будет снята. Блокировка снимается по завершении защищаемого ею фрагмента кода.
Так же для блокировки объектов можно применять конструкция lock (this). Но данную конструкцию следует применять в том случае, если this является ссылкой на закрытый объект.
Пример работы распараллеленного приложения без синхронизации потоков:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace OperatorLock
{
class Work
{
int i, j;
public void ThreadStart()
{
if (i != j)
Console.WriteLine("Ошибка");
i++;
Thread.Sleep(200);
j++;
Console.WriteLine("{0},{1}", i, j);
}
}
class Program
{
static void Main(string[] args)
{
Work store = new Work();
for (int i = 0; i < 10; ++i)
{
new Thread(new ThreadStart(store.ThreadStart)).Start();
Thread.Sleep(100);
}
Console.ReadLine();
}
}
}
В методе Main() - метод ThreadStar() 10 раз, в отдельных потоках. Запустим программу. В случайном порядке выведется на экран цифры и надпись "ошибка" (Рис. 5.1).
увеличить изображение
Рис. 5.1.Результат выполнения программы без оператора lock
Такой результат получается потому, что приращение значения переменной i и переменой j происходит не сразу, а с небольшой задержкой (в данном случае искусственной, полученной с помощью метода Sleep(), который усыпляет поток на 200 милисекунд). Именно на этом моменте происходит рассинхронизация потоков. Для синхронизации потоков используется оператор блокировки lock. Модифицируем пример, добавив в него синхронизацию:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace OperatorLock
{
class Work
{
int i, j;
object LockObj = new object();
public void ThreadStart()
{
lock (LockObj)
{
if (i != j)
Console.WriteLine("Ошибка");
i++;
Thread.Sleep(200);
j++;
Console.WriteLine("{0},{1}", i, j);
}
}
}
class Program
{
static void Main(string[] args)
{
Work store = new Work();
for (int i = 0; i < 10; ++i)
{
new Thread(new ThreadStart(store.ThreadStart)).Start();
Thread.Sleep(100);
}
Console.ReadLine();
}
}
}
Как только поток войдет в контекст lock, маркер блокировки (в данном случае - текущий объект) станет недоступным другим потокам до тех пор, пока блокировка не будет снята по выходе из контекста lock. Если теперь запустить приложение, можно увидеть, что каждый поток получил возможность выполнить свою работу до конца (Рис. 5.2).
увеличить изображение
Рис. 5.2.Результат выполнения программы с оператором lock
Классы синхронизации в .NET Framework
Interlocked
Класс Interlocked предоставляет атомарные операции для переменных, общедоступных нескольким потокам. К примеру, операции инкремента (i++) и декремента (i--) которые не являются безопасными при многопоточной обработке. Такие операции могут прерываться планировщиком потоков. Класс Interlocked позволяет выполнять операции инкремента, декремента, обмена и считывания значений, в безопасной к потокам манере.
Применение класса Interlocked является гораздо более быстрым подходом по сравнению с остальными приемами по обеспечению синхронизации. Однако пользоваться им можно для устранения только простых последствий синхронизации.
Таблица 5.1. Методы класса Interlocked | |
Имя | Описание |
CompareExchange() | Безопасно проверяет два значения на эквивалентность. Если они эквивалентны, изменяет одно из значений на третье |
Decrement() | Безопасно уменьшает значение на 1 |
Exchange() | Безопасно меняет два значения местами |
Increment() | Безопасно увеличивает значение на 1 |
Можно так же использовать оператор lock для блокирования доступа к переменной при установке для нее нового значения, взамен метода Increment():
lock(x)
{
x++;
}
Но можно воспользоваться классом Interlocked. Метод Increment() не только изменяет значение входного параметра, но также возвращает полученное новое значение:
public void Proc()
{
int x = Interlocked.Increment(ref intVal);
}
В дополнение, методы Increment() и Decrement() позволяют автоматически присваивать значения переменным. В примере переменной x присваивается значение 2:
public int x;
public void Proc ()
{
Interlocked.Exchange(ref x, 2);
}
Класс Monitor
Класс Monitor предназначен для того, чтобы контролировать доступ к объектам, предоставляя блокировку объекта одному потоку. Блокировки объектов предоставляют возможность ограничения доступа к части кода, обычно называемой критической секцией. Пока поток владеет блокировкой для объекта, никакой другой поток не может ею завладеть.
В классе Monitor определено несколько методов синхронизации. Например, чтобы получить возможность блокировки для некоторого объекта, вызывается метод Enter(), а чтобы снять блокировку - метод Exit(). Эти методы имеют следующий формат:
public static void Enter(object syncOb)
public static void Exit(object syncOb)
где syncOb - синхронизируемый объект. Если при вызове метода Enter() заданный объект недоступен, вызывающий поток будет ожидать до тех пор, пока объект не станет доступным.
Использование оператора lock эквивалентно вызову метода Enter() с последующим вызовом метода Exit() класса Monitor. Класс Monitor обладает одним важным преимуществом по сравнению с оператором lock: он позволяет добавлять значение тайм-аута для ожидания получения блокировки. Таким образом, вместо того, чтобы ожидать блокировку до бесконечности, можно вызвать метод TryEnter() и передать в нем значение тайм-аута, указывающее, сколько максимум времени должно ожидаться получение блокировки. Один из форматов его использования метода TryEnter():
public static bool TryEnter(object syncOb)
Метод возвращает значение true, если вызывающий поток получает блокировку для объекта syncOb, и значение false в противном случае. Если заданный объект недоступен, вызывающий поток будет ожидать до тех пор, пока он не станет доступным.
Дата добавления: 2017-06-02; просмотров: 625;