Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Многопоточность.doc
Скачиваний:
4
Добавлен:
28.10.2018
Размер:
225.79 Кб
Скачать

Класс Mutex

Как видно из предыдущего примера, оператор lock и класс ReaderWriterLock позволяют эффективно производить синхронизацию потоков приложения. Однако иногда требуется синхронизация не только в рамках одного процесса, но и в пределах нескольких процессов. Классическим примером является создание приложения, у которого может быть запущен только единственный экземпляр. Несомненно, .NET имеет более гибкие механизмы, обеспечивающие подобное поведение приложения, но мы рассмотрим простейший способ, подразумевающий использование класса Mutex (программа ThreadingMutex).

bool isCreated;

Mutex mtx = new Mutex(true, "SingleThreadingMutex", out isCreated);

if (!isCreated)

{

Console.WriteLine("Application is already running.");

Console.ReadLine();

return;

}

Console.WriteLine("Application works.");

Console.ReadLine();

mtx.Close();

Рассмотрим его применение. Конструктор класса Mutex имеет несколько перегруженных версий. Используемая нами принимает 3 параметра. Первый из них говорит, должны ли мы сразу получить мьютекс в эксклюзивное пользование (владеть им). В этом случае класс Mutex создает внутри себя глобальный для операционной системы объект с заданным именем, которое передается в качестве второго параметра конструктора. Именно потому, что этот объект является глобальным для операционной системы, мьютекс годен для синхронизации между процессами. Если такой объект с этим имененм уже существует, то создать его заново не удастся. Об этом сигнализирует третий параметр конструктора. Таким образом, только запущенный первым экземпляр приложения сумеет создать мьютекс. Всем остальным это не удастся и они получат соответствующее уведомление через переменную isCreated.

После окончания работы вашего приложения мьютекс нужно закрыть:

mtx.Close();

Эта команда уничтожает созданный глобальный объект и теперь его можно создавать заново.

Кроме описанных выше возможностей класс Mutex является наследником класса WaitHandle и обладает всеми его возможностями.

Класс WaitHandle

Класс WaitHandle разработан для поддержки потоками ожидания завершения различных задач. Сам этот класс является абстрактным, но имеет несколько полезных методов. К ним относятся WaitAll, позволяющий дождаться освобождения всех объектов WaitHandle, переданных ему в массиве в качестве параметра, и WaitAny, позволяющий дождаться первого освобождения одного из объектов WaitHandle, переданных ему в массиве в качестве параметра.

Легче всего рассмотреть работу с этим классом на примере его наследников. Обычно используют два из них: AutoResetEvent и ManualResetEvent. Эти классы обладают так же поддержкой возможности уведомления ожидающих потоков о том, что ожидаемое ими событие произошло.

Класс AutoResetEvent

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

public class Integrator

{

/// <summary>

/// Класс отрезка интегрирование

/// </summary>

private class IntegrationRange

{

public double Start { get; set; }

public double Finish { get; set; }

}

private double firstPartResult = 0;

private double secondPartResult = 0;

private double tolerance = 0.00000001;

private WaitHandle[] handles = new WaitHandle[]

{

new AutoResetEvent(false),

new AutoResetEvent(false)

};

public double GetIntegral(double start, double finish)

{

double center = (start + finish) / 2.0;

ThreadPool.QueueUserWorkItem(CalcFirstPart, new IntegrationRange() { Start = start, Finish = center });

ThreadPool.QueueUserWorkItem(CalcSecondPart, new IntegrationRange() { Start = center, Finish = finish });

WaitHandle.WaitAll(handles);

return firstPartResult + secondPartResult;

}

private void CalcFirstPart(object state)

{

IntegrationRange range = (IntegrationRange)state;

firstPartResult = CalcIntegral(range.Start, range.Finish);

(handles[0] as AutoResetEvent).Set();

}

private void CalcSecondPart(object state)

{

IntegrationRange range = (IntegrationRange)state;

secondPartResult = CalcIntegral(range.Start, range.Finish);

(handles[1] as AutoResetEvent).Set();

}

private double CalcIntegral(double start, double finish)

{

double i1 = 0;

double i2 = 0;

int n = 100;

do

{

i1 = i2;

i2 = 0;

for (int i = 0; i < n; i++)

{

double x = start + (finish - start) * i / n;

i2 += (x * x) * (finish - start) / n;

}

n *= 2;

}

while (Math.Abs(i1 - i2) > tolerance);

return i2;

}

}

Приведенный здесь код класса выполняет данную работу. Рассмотрим, как он это делает. Его единственный открытый метод GetIntegral, который фактически запускает в отдельных потоках 2 метода, которые считают первую и вторую части интеграла:

ThreadPool.QueueUserWorkItem(CalcFirstPart, new IntegrationRange() { Start = start, Finish = center });

ThreadPool.QueueUserWorkItem(CalcSecondPart, new IntegrationRange() { Start = center, Finish = finish });

Далее метод GetIntegral ожидает завершение работы обоих этих потоков. Для этого он использует метод WaitAll класса WaitHandle. Как уже говорилось, этот метод ожидает, пока все переданные ему в массиве объекты WaitHandle не сообщат ему, что они свободны. Тонкостью здесь является только то, что в некоторых операционных системах этот метод не может работать более чем с 64 объектами WaitHandle.

Массив, который передается методу WaitAll, содержит объекты AutoResetEvent.

private WaitHandle[] handles = new WaitHandle[]

{

new AutoResetEvent(false),

new AutoResetEvent(false)

};

Конструктору этих объектов передается флаг, указывающий, в каком состоянии они находятся: свободно (true) или занято (false). В данном случае все объекты AutoResetEvent заняты, и потоку придется ждать их освобождения.

Методы CalcFirstPart и CalcSecondPart вычисляют интегралы и сигнализируют о завершении своей работы, вызывая метод Set объектов AutoResetEvent. Этот метод работает следующим образом. Предположим, что есть несколько потоков, которые ждут освобождения конкретного объекта AutoResetEvent. Вызов метода Set этого объекта закончит ожидание только одного ждущего потока, остальные потоки продолжат свое ожидание до следующего вызова метода Set. Если после очередного вызова этого метода не осталось более ждущих потоков, то объект AutoResetEvent переходит в состояние «свободно». И для того, чтобы снова перевести его в состояние занято, нужно вызвать его метод Reset.