Одно из основных назначений средств синхронизации заключается в организации взаимно исключительного доступа к разделяемому ресурсу. Изменения общих данных одним потоком не должны прерываться другими потоками. Фрагмент кода, в котором осуществляется работа с разделяемым ресурсом и который должен выполняться только в одном потоке одновременно, называется критической секцией.
public string data; void DoSomeWork1() { Thread.Name = "First"; data = "AAAA"; Console.WriteLine("Thread: {0}, Data: {1}", Thread.Name, data); } void DoSomeWork2() { Thread.Name = "Second"; data = "BBBB"; Console.WriteLine("Thread: {0}, Data: {1}", Thread.Name, data); }
В этом фрагменте предполагается, что два потока изменяют значение общей переменной data. После внесения изменений поток выводит на экран новое значение data и имя потока. Отсутствие средств синхронизации могло бы привести к некорректному результату:
Thread: First, Data: BBBBB Thread: Second, Data: BBBBB
Поток First внес свои изменения и собирался вывести сообщение, но второй поток успел вклиниться и изменить данные.
Выделение критической секции с помощью конструкции lock позволяет избежать такой ситуации:
lock(sync_obj) { data = "AAAA"; Console.WriteLine("Thread #1 has changed data to: {0}", data); }
Когда один поток входит в критическую секцию (захватывает объект синхронизации), другой поток ожидает завершения всего блока (освобождения объекта синхронизации). Если заблокировано было несколько потоков, то при освобождении критической секции только один поток разблокируется и входит критическую секцию.
Для выделения критической секции с помощью конструкции lock необходимо указать объект синхронизации, который выступает в качестве идентификатора блокировки.