Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Многопоточность требует тщательного программирования. Для большинства задач можно уменьшить сложность, организовывая выполнение запросов потоками из пула. В этом разделе рассматриваются более сложные ситуации, такие как координация работы нескольких потоков или обработка потоков, которые блокируются.
Замечание
Начиная с .NET Framework 4, библиотека параллельных задач и PLINQ предоставляют API,которые снижают сложность и риски многопотокового программирования. Дополнительные сведения см. в статье "Параллельное программирование" в .NET.
Взаимоблокировки и условия гонки
Многопоточность решает проблемы с пропускной способностью и скоростью реагирования, но при этом он вводит новые проблемы: взаимоблокировки и условия гонки.
Взаимоблокировки
Взаимоблокировка возникает, когда каждый из двух потоков пытается заблокировать ресурс, который другой уже заблокирован. Ни один поток не может добиться дальнейшего прогресса.
Многие методы управляемых классов потоков предоставляют тайм-ауты, чтобы помочь обнаружить взаимоблокировки. Например, следующий код пытается получить блокировку для объекта с именем lockObject
. Если блокировка не будет установлена в течение 300 миллисекунд, Monitor.TryEnter вернёт false
.
If Monitor.TryEnter(lockObject, 300) Then
Try
' Place code protected by the Monitor here.
Finally
Monitor.Exit(lockObject)
End Try
Else
' Code to execute if the attempt times out.
End If
if (Monitor.TryEnter(lockObject, 300)) {
try {
// Place code protected by the Monitor here.
}
finally {
Monitor.Exit(lockObject);
}
}
else {
// Code to execute if the attempt times out.
}
Условия гонки
Состояние гонки — это ошибка, возникающая, когда результат программы зависит от того, какой из двух или нескольких потоков достигает определенного блока кода. Выполнение программы много раз выдает различные результаты, и результат любого заданного выполнения не может быть предсказан.
Простой пример состояния гонки — увеличение поля. Предположим, что класс имеет частное статическое поле (shared in Visual Basic), которое увеличивается каждый раз при создании экземпляра класса, используя код, например objCt++;
(C#) или objCt += 1
(Visual Basic). Для этой операции требуется загрузка значения в регистр из objCt
, увеличение значения и его хранение в objCt
.
В многопоточном приложении поток, который загрузил и увеличил значение, может быть вытеснен другим потоком, который выполняет все три шага; когда первый поток возобновляет выполнение и сохраняет свое значение, он перезаписывает objCt
, не учитывая, что значение тем временем изменилось.
Эту конкретную ситуацию гонки легко избежать, используя методы класса Interlocked, такие как Interlocked.Increment. Дополнительные сведения о других методах синхронизации данных между несколькими потоками см. в разделе "Синхронизация данных для многопоточности".
Условия гонки также могут возникать при синхронизации действий нескольких потоков. При написании строки кода необходимо учитывать, что может произойти, если поток был приостановлен перед выполнением строки (или перед любой из отдельных команд процессора, составляющих строку), и другой поток продолжил выполнение вместо него.
Статические элементы и статические конструкторы
Класс не инициализируется, пока его конструктор класса (static
конструктор в C#, Shared Sub New
в Visual Basic) не завершит работу. Чтобы предотвратить выполнение кода в типе, который не инициализирован, среда CLR блокирует все вызовы из других потоков к членам класса (static
члены Shared
в Visual Basic), пока конструктор классов не завершит работу.
Например, если конструктор класса запускает новый поток, а функция потока вызывает static
член класса, новый поток блокируется, пока конструктор класса не завершит свою работу.
Это относится к любому типу, который может иметь static
конструктор.
Количество процессоров
Существует ли несколько процессоров или только один процессор, доступный в системе, может влиять на многопототочную архитектуру. Дополнительные сведения см. в разделе "Число процессоров".
Environment.ProcessorCount Используйте свойство, чтобы определить количество процессоров, доступных во время выполнения.
Общие рекомендации
При использовании нескольких потоков следует учитывать следующие рекомендации.
Не используйте Thread.Abort для завершения других потоков. Вызов
Abort
в другом потоке подобен выбросу исключения в этом потоке, не зная, на каком этапе обработки этот поток находится.Не используйте Thread.Suspend и Thread.Resume для синхронизации действий нескольких потоков. Используйте Mutex, и ManualResetEventAutoResetEventMonitor.
Не управляйте выполнением рабочих потоков из основной программы (например, с помощью событий). Вместо этого создайте программу таким образом, чтобы рабочие потоки были ответственны за ожидание, пока работа не будет доступна, выполнение её и уведомление других частей программы после завершения. Если рабочие потоки не блокируются, рекомендуется использовать потоки из пула потоков. Monitor.PulseAll полезно в ситуациях, когда происходит блокировка рабочих потоков.
Не используйте типы в качестве объектов блокировки. То есть избегать кода, такого как
lock(typeof(X))
в C# илиSyncLock(GetType(X))
в Visual Basic, или использования Monitor.Enter с объектами Type. Для данного типа существует только один экземпляр System.Type для каждого домена приложения. Если тип, на который вы устанавливаете блокировку, является общедоступным, то другой код может также блокировать его, что ведет к взаимоблокировкам. Дополнительные сведения см. в разделе "Рекомендации по надежности".Будьте осторожны при блокировке экземпляров, например
lock(this)
в C# илиSyncLock(Me)
в Visual Basic. Если другой код в приложении, внешний к типу, принимает блокировку объекта, может произойти взаимоблокировка.Убедитесь, что поток, который вошел в монитор, всегда покидает монитор, даже если возникает исключение, когда поток находится в мониторе. Оператор lock в C# и оператор SyncLock в Visual Basic обеспечивают это поведение автоматически, используя блок finally для гарантии вызова Monitor.Exit. Если вы не можете убедиться, что выход будет вызван, рассмотрите возможность изменения структуры для использования Мьютекса. Мьютекс автоматически освобождается, когда завершается поток, который в данный момент его владеет.
Используйте несколько потоков для задач, требующих разных ресурсов, и избегайте назначения нескольких потоков одному ресурсу. Например, любая задача, требующая операций ввода-вывода, имеет собственный поток, поскольку этот поток блокируется во время таких операций, что позволяет другим потокам выполняться. Входные данные пользователя — это другой ресурс, который использует выделенный поток. На компьютере с одним процессором задача, которая включает в себя интенсивные вычисления, сосуществует с вводом пользователя и задачами, которые включают операции ввода-вывода, но несколько вычислительно сложных задач конкурируют друг с другом.
Используйте методы класса Interlocked для простых изменений состояния вместо использования оператора
lock
(SyncLock
в Visual Basic).lock
— это хорошее средство общего назначения, но класс Interlocked обеспечивает более высокую производительность для обновлений, которые должны быть атомарными. Внутри системы выполняется единственный префикс блокировки, если нет конфликтов. В проверках кода обратите внимание на код, как в примерах ниже. В первом примере переменная состояния увеличивается:SyncLock lockObject myField += 1 End SyncLock
lock(lockObject) { myField++; }
Вы можете повысить производительность, используя Increment метод вместо
lock
инструкции, как показано ниже.System.Threading.Interlocked.Increment(myField)
System.Threading.Interlocked.Increment(myField);
Замечание
Используйте метод Add для атомарных инкрементов, превышающих 1.
Во втором примере переменная ссылочного типа обновляется только в том случае, если она является пустой ссылкой (
Nothing
в Visual Basic).If x Is Nothing Then SyncLock lockObject If x Is Nothing Then x = y End If End SyncLock End If
if (x == null) { lock (lockObject) { x ??= y; } }
Производительность можно улучшить с помощью CompareExchange метода, как показано ниже.
System.Threading.Interlocked.CompareExchange(x, y, Nothing)
System.Threading.Interlocked.CompareExchange(ref x, y, null);
Замечание
Перегрузка CompareExchange<T>(T, T, T) метода предоставляет типобезопасную альтернативу для ссылочных типов.
Рекомендации по библиотекам классов
При проектировании библиотек классов для многопоточной работы следует учитывать следующие рекомендации.
По возможности избегайте необходимости синхронизации. Это особенно верно для часто используемого кода. Например, алгоритм может быть скорректирован, чтобы терпеть состояние гонки, а не устранять его. Ненужная синхронизация снижает производительность и создает возможность возникновения ситуаций взаимоблокировок и состояний гонки.
По умолчанию обеспечить безопасность потоков статических данных (
Shared
в Visual Basic).Не делайте поток данных экземпляра безопасным по умолчанию. Добавление блокировок для создания потокобезопасного кода снижает производительность, увеличивает конкуренцию за блокировки и создает возможность возникновения взаимоблокировок. В распространенных моделях приложений только один поток за раз выполняет пользовательский код, что сводит к минимуму потребность в безопасности потоков. По этой причине библиотеки классов .NET не являются потокобезопасны по умолчанию.
Избегайте предоставления статических методов, которые изменяют статическое состояние. В распространенных сценариях сервера статическое состояние разделяется между запросами, что означает, что несколько потоков могут одновременно выполнять рассчитанный на это код. Откроется возможность ошибок многопоточности. Рекомендуется использовать шаблон проектирования, который инкапсулирует данные в экземпляры, которые не разделяются между запросами. Кроме того, если статические данные синхронизированы, вызовы между статическими методами, которые изменяют состояние, могут привести к взаимоблокировке или избыточной синхронизации, отрицательно влияя на производительность.