C# 4.0 полное руководство - 2011
Шрифт:
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.
Рассмотрим эту программу
Оператор lock в методе Sumlt препятствует одновременному использованию данного метода в разных потоках. Обратите внимание на то, что в операторе lock объект lockOn используется в качестве синхронизируемого. Это закрытый объект, предназначенный исключительно для синхронизации. Метод Sleep намеренно вызывается для того, чтобы произошло переключение задач, хотя в данном случае это невозможно. Код в методе Sumlt заблокирован, и поэтому он может быть одновременно использован только в одном потоке. Таким образом, когда начинает выполняться второй порожденный поток, он не сможет войти в метод Sumlt до тех пор, пока из него не выйдет первый порожденный поток. Благодаря этому гарантируется получение правильного результата.
Для того чтобы полностью уяснить принцип действия блокировки, попробуйте удалить из рассматриваемой здесь программы тело метода Sumlt . В итоге метод Sumlt перестанет быть синхронизированным, а следовательно, он_может параллельно использоваться в любом числе потоков для одного и того же объекта. Поскольку текущая сумма сохраняется в поле sum, она может быть изменена в каждом потоке, вызывающем метод Sumlt . Это означает, что если два потока одновременно вызывают метод Sumlt для одного и того же объекта, то конечный результат получается неверным, поскольку содержимое поля sum отражает смешанный результат суммирования в обоих потоках. В качестве примера ниже приведен результат выполнения рассматриваемой здесь программы после снятия блокировки с метода Sumlt .
Потомок #1 начат.
Сумма для потока Потомок #1 равна 2 9 Потомок #1 завершен.
Текущая
Потомок #2 завершен.
Как следует из приведенного выше результата, в обоих порожденных потоках метод Sumlt используется одновременно для одного и того же объекта, а это приводит к искажению значения в поле sum.
Ниже подведены краткие итоги использования блокировки.
• Если блокировка любого заданного объекта получена в одном потоке, то после блокировки объекта она не может быть получена в другом потоке.
• Остальным потокам, пытающимся получить блокировку того же самого объекта, придется ждать до тех пор, пока объект не окажется в разблокированном состоянии.
• Когда поток выходит из заблокированного фрагмента кода, соответствующий объект разблокируется.
Другой подход к синхронизации потоков
Несмотря на всю простоту и эффективность блокировки кода метода, как показано в приведенном выше примере, такое средство синхронизации оказывается пригодным далеко не всегда. Допустим, что требуется синхронизировать доступ к методу класса, который был создан кем-то другим и сам не синхронизирован. Подобная ситуация вполне возможна при использовании чужого класса, исходный код которого недоступен. В этом случае оператор lock нельзя ввести в соответствующий метод чужого класса. Как же тогда синхронизировать объект такого класса? К счастью, этот вопрос разрешается довольно просто: доступ к объекту может быть заблокирован из внешнего кода по отношению к данному объекту, для чего достаточно указать этот объект в операторе lock. В качестве примера ниже приведен другой вариант реализации предыдущей программы. Обратите внимание на то, что код в методе Sumlt уже не является заблокированным, а объект lockOn больше не объявляется. Вместо этого вызовы метода Sumlt блокируются в классе MyThread.
// Другой способ блокировки для синхронизации доступа к объекту, using System;
using System.Threading;
class SumArray { int sum;
public int Sumlt(int[] nums) {
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 + " начат.");
// Заблокировать вызовы метода Sumlt. lock(sa) answer = sa.Sumlt(a);
Console.WriteLine("Сумма для потока " + Thrd.Name +
" равна " + answer);
Console.WriteLine(Thrd.Name + " завершен.");