Выбрать главу

Ниже приведена общая форма блокировки:

lock(lockObj) {

// синхронизируемые операторы

}

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

Блокируемым считается такой объект, который представляет синхронизируемый ресурс. В некоторых случаях им оказывается экземпляр самого ресурса или же произвольный экземпляр объекта, используемого для синхронизации. Следует, однако, иметь в виду, что блокируемый объект не должен быть общедоступным, так как в противном случае он может быть заблокирован из другого, неконтролируемого в программе фрагмента кода и в дальнейшем вообще не разблокируется. В прошлом для блокировки объектов очень часто применялась конструкция lock (this). Но она пригодна только в том случае, если this является ссылкой на закрытый объект. В связи с возможными программными и концептуальными ошибками, к которым может привести конструкция lock (this), применять ее больше не рекомендуется. Вместо нее лучше создать закрытый объект, чтобы затем заблокировать его. Именно такой подход принят в примерах программ, приведенных далее в этой главе. Но в унаследованном коде C# могут быть обнаружены примеры применения конструкции lock (this). В одних случаях такой код оказывается безопасным, а в других — требует изменений во избежание серьезных осложнений при его выполнении.

В приведенной ниже программе синхронизация демонстрируется на примере управления доступом к методу Sumlt (), суммирующему элементы целочисленного массива.

// Использовать блокировку для синхронизации доступа к объекту.

using System;

using System.Threading;

class SumArray { int sum;

object lockOn = new object(); // закрытый объект, доступный

// для последующей блокировки

lock(lockOn) { // заблокировать весь метод

sum =0; // установить исходное значение суммы

for(int i=0; i < nums.Length; i++)    {

sum +- nums[i];

Console.WriteLine("Текущая сумма для потока " +

Thread.CurrentThread.Name + " равна " + sum); Thread.Sleep(10); // разрешить переключение задач

}

return sum;

}

}

}

class MyThread {

public Thread Thrd; int[] a; int answer;

// Создать один объект типа SumArray для всех // экземпляров класса MyThread. static SumArray sa = new SumArray();

// Сконструировать новый поток, public MyThread(string name, int [ ] nums) { a = nums;

Thrd = new Thread(this.Run);

Thrd.Name = name;

Thrd.Start(); // начать поток

}

// Начать выполнение нового потока, void Run()    {

Console.WriteLine(Thrd.Name + " начат.");

answer = sa.Sumlt(a);

Console.WriteLine("Сумма для потока " + Thrd.Name + " равна " + answer); Console.WriteLine(Thrd.Name + " завершен.");

}

}

class Sync {

static void Main() {

int[] a = {1, 2, 3, 4, 5};

MyThread mtl = new MyThread ("Потомок #1", a);.

MyThread mt2 = new MyThread("Потомок #2", a);

mtl.Thrd.Join(); mt2.Thrd.Join() ;

Ниже приведен результат выполнения данной программы, хотя у вас он может оказаться несколько иным.

Потомок #1 начат.

Сумма для потока Потомок #2 равна 15 Потомок #2 завершен.

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

Рассмотрим эту программу более подробно. Сначала в ней создаются три класса. Первым из них оказывается класс SumArray, в котором определяется метод Sumlt (), суммирующий элементы целочисленного массива. Вторым создается класс MyThread, в котором используется статический объект sa типа SumArray. Следовательно, единственный объект типа SumArray используется всеми объектами типа MyThread. С помощью этого объекта получается сумма элементов целочисленного массива. Обратите внимание на то, что текущая сумма запоминается в поле sum объекта типа SumArray. Поэтому если метод Sumlt () используется параллельно в двух потоках, то оба потока попытаются обратиться к полю sum, чтобы сохранить в нем текущую сумму. А поскольку это может привести к ошибкам, то доступ к методу Sumlt () должен быть синхронизирован. И наконец, в третьем классе, Sync, создаются два потока, в которых подсчитывается сумма элементов целочисленного массива.