Потокобезопасные компоненты
В многопоточном программировании часто возникает необходимость совместного использования ресурсов между потоками. Например, нескольким потокам может потребоваться доступ к общей базе данных или к набору системных переменных с целью их обновления. Если несколько потоков пытаются одновременно получить доступ к общим ресурсам, может возникнуть состояние гонки. Состояние гонки возникает, когда какой-либо поток изменяет состояние ресурса на недопустимое, а затем другой поток пытается получить доступ к этому ресурсу и использовать его в этом недопустимом состоянии. Рассмотрим следующий пример.
Public Class WidgetManipulator
Public TotalWidgets as Integer = 0
Public Sub AddWidget()
TotalWidgets += 1
Console.WriteLine("Total widgets = " & TotalWidgets.ToString)
End Sub
Public Sub RemoveWidgets()
TotalWidgets -= 10
End Sub
End Class
public class WidgetManipulator
{
public int TotalWidgets = 0;
public void AddWidget()
{
TotalWidgets++;
Console.WriteLine("Total widgets = " + TotalWidgets.ToString());
}
public void RemoveWidgets()
{
TotalWidgets -= 10;
}
}
Этот класс предоставляет два метода. Один метод, AddWidget, прибавляет 1 к значению поля TotalWidgets и выводит полученное значение на консоль. Второй метод вычитает число 10 из значения TotalWidgets. Если два потока одновременно попытаются получить доступ к одному и тому же экземпляру класса WidgetManipulator, может произойти следующее. Один поток может вызвать метод AddWidget, в то время как другой поток вызвал метод RemoveWidgets. В этом случае значение TotalWidgets может быть изменено вторым потоком до того, как первый поток сообщит точный результат. Состояние гонки может привести к неточным результатам и вызвать повреждение данных.
Предотвращение состояния гонки путем использования блокировок
Наиболее важные части кода можно защитить от состояния гонки путем применения блокировок. Блокировка, задаваемая ключевым словом оператора SyncLock в Visual Basic или ключевым словом оператора lock в C#, позволяет одному потоку получить права на монопольное выполнение для некоторого объекта. Блокировки показаны в следующем примере.
SyncLock MyObject
' Insert code that affects MyObject.
End SyncLock
lock(MyObject)
{
// Insert code that affects MyObject.
}
Если введен оператор "lock", выполнение для указанного объекта (в предыдущем примере объекта MyObject) блокируется до тех пор, пока указанный поток не получит монопольный доступ к с рассматриваемому объекту. При достижении конца блокировки она снимается и выполнение продолжается в обычном режиме. Заблокировать можно только объект, возвращающий ссылку. Таким способом не может быть заблокирован объект типа значения.
Недостатки блокировок
Использование блокировок гарантирует, что объект не будет использоваться одновременно несколькими потоками, однако блокировка может вызвать значительное снижение быстродействия. Допустим, что в какой-либо программе выполняется множество различных потоков. Если в каждом потоке требуется использовать определенный объект и перед этим поток должен ждать установки монопольной блокировки для этого объекта, то выполнение всех потоков прекратится и образуется очередь, что приведет к снижению производительности. Поэтому блокировки следует использовать только при наличии кода, который должен выполняться как единый блок. Например, этот код может обновлять несколько ресурсов, зависящих друг от друга. Такой код называется атомарным. Если разрешить блокировку только для кода, который должен выполняться единым блоком, это позволит создавать многопоточные компоненты, которые гарантируют безопасность данных и при этом обеспечивают хорошую производительность.
Нужно также стараться избегать ситуаций, которые могут вызвать взаимоблокировку. В этом случае несколько потоков ждут друг от друга освобождения общих ресурсов. Предположим, например, что поток 1 установил блокировку ресурса А и ожидает доступа к ресурсу В. Одновременно с этим поток 2 установил блокировку ресурса В и ждет освобождения ресурса А. В таком случае ни один поток не будет работать. Чтобы избежать подобных ситуаций, необходимо соблюдать аккуратность при программировании.
См. также
Задачи
Практическое руководство. Координирование нескольких потоков выполнения
Практическое руководство. Управление элементами управления из потоков
Пошаговое руководство. Разработка простого многопоточного компонента с использованием Visual Basic
Пошаговое руководство. Разработка простого многопоточного компонента с помощью Visual C#
Ссылки
Основные понятия
Обзор асинхронной модели, основанной на событиях