- Управляемая поточность Managed threading
- В этом разделе In This Section
- Справочник Reference
- Связанные разделы Related Sections
- Рекомендации по работе с потоками Managed threading best practices
- Взаимоблокировки и состояние гонки Deadlocks and race conditions
- Взаимоблокировки Deadlocks
- Состояние гонки Race conditions
- Статические члены и статические конструкторы Static members and static constructors
- Число процессоров Number of processors
- Основные рекомендации General recommendations
- Рекомендации для библиотек классов Recommendations for class libraries
Управляемая поточность Managed threading
При разработке для компьютеров с одним или с несколькими процессорами необходимо, чтобы приложение обеспечивало наиболее эффективное взаимодействие с пользователем, даже если в приложении выполняются другие задачи. Whether you’re developing for computers with one processor or several, you want your application to provide the most responsive interaction with the user, even if the application is currently doing other work. Использование нескольких потоков выполнения — это один из способов обеспечить в приложении возможность реагирования на действия пользователя при одновременном использовании процессора для выполнения задач между появлением или даже во время появления событий пользователя. Using multiple threads of execution is one of the most powerful ways to keep your application responsive to the user and at the same time make use of the processor in between or even during user events. Хотя в этом разделе приведены основные сведения о потоковом выполнении, здесь также рассматриваются принципы работы управляемых потоков и их использование. While this section introduces the basic concepts of threading, it focuses on managed threading concepts and using managed threading.
Начиная с .NET Framework 4 многопоточное программирование значительно упростилось благодаря классам System.Threading.Tasks.Parallel и System.Threading.Tasks.Task, Parallel LINQ (PLINQ), классам параллельных коллекций из пространства имен System.Collections.Concurrent и модели программирования, которая вместо потоков использует концепцию задач. Starting with .NET Framework 4, multithreaded programming is greatly simplified with the System.Threading.Tasks.Parallel and System.Threading.Tasks.Task classes, Parallel LINQ (PLINQ), concurrent collection classes in the System.Collections.Concurrent namespace, and a programming model that is based on the concept of tasks rather than threads. Дополнительные сведения см. в разделе Параллельное программирование. For more information, see Parallel Programming.
В этом разделе In This Section
Основы управляемых потоков Managed Threading Basics
Обзор управляемых потоков и случаев использования нескольких потоков. Provides an overview of managed threading and discusses when to use multiple threads.
Использование потоков и работа с потоками Using Threads and Threading
Описание способов создания, запуска, приостановки, возобновления и отмены потоков. Explains how to create, start, pause, resume, and abort threads.
Рекомендации по работе с потоками Managed Threading Best Practices
Обсуждение уровней синхронизации, способов предотвращения взаимоблокировки и состояния гонки и других проблем, связанных с многопоточностью. Discusses levels of synchronization, how to avoid deadlocks and race conditions, and other threading issues.
Объекты и функциональные возможности работы с потоками Threading Objects and Features
Описание управляемых классов, которые можно использовать для синхронизации действий потоков и данных объектов, доступных в разных потоках, а также обзор пула потоков. Describes the managed classes you can use to synchronize the activities of threads and the data of objects accessed on different threads, and provides an overview of thread pool threads.
Справочник Reference
System.Threading
Содержит классы для использования и синхронизации управляемых потоков. Contains classes for using and synchronizing managed threads.
System.Collections.Concurrent
Содержит классы коллекций, которые могут безопасно использоваться несколькими потоками. Contains collection classes that are safe for use with multiple threads.
System.Threading.Tasks
Содержит классы для создания и планирования задач параллельной обработки. Contains classes for creating and scheduling concurrent processing tasks.
Связанные разделы Related Sections
Домены приложений Application Domains
Общие сведения о доменах приложений и их использовании в Common Language Infrastructure. Provides an overview of application domains and their use by the Common Language Infrastructure.
Асинхронный файловый ввод-вывод Asynchronous File I/O
Описывает преимущества и основные операции асинхронного ввода и вывода. Describes the performance advantages and basic operation of asynchronous I/O.
Task-based Asynchronous Pattern (TAP) (Асинхронный шаблон, основанный на задачах (TAP)) Task-based Asynchronous Pattern (TAP)
Предоставляет общие рекомендации по асинхронному программированию в .NET. Provides an overview of the recommended pattern for asynchronous programming in .NET.
Асинхронный вызов синхронных методов Calling Synchronous Methods Asynchronously
Описание способов вызова методов в потоках пула потоков с помощью встроенных функций делегатов. Explains how to call methods on thread pool threads using built-in features of delegates.
Параллельное программирование Parallel Programming
Описание библиотек параллельного программирования, упрощающих использование нескольких потоков в приложениях. Describes the parallel programming libraries, which simplify the use of multiple threads in applications.
Parallel LINQ (PLINQ) Parallel LINQ (PLINQ)
Описание системы для выполнения запросов в параллельном режиме, позволяющей воспользоваться преимуществами нескольких процессоров. Describes a system for running queries in parallel, to take advantage of multiple processors.
Рекомендации по работе с потоками Managed threading best practices
Многопоточность требует тщательного программирования. Multithreading requires careful programming. Большинство задач можно упростить, поместив запросы на выполнение в очередь по потокам пулов потоков. For most tasks, you can reduce complexity by queuing requests for execution by thread pool threads. В этом разделе рассматриваются более сложные ситуации, такие как координация работы нескольких потоков или обработка потоков, вызывающих блокировку. This topic addresses more difficult situations, such as coordinating the work of multiple threads, or handling threads that block.
Начиная с версии .NET Framework 4, библиотека параллельных задач и PLINQ предоставляют интерфейсы API, которые несколько снижают сложность и риски многопоточного программирования. Starting with .NET Framework 4, the Task Parallel Library and PLINQ provide APIs that reduce some of the complexity and risks of multi-threaded programming. Дополнительные сведения см. в статье Параллельное программирование в .NET. For more information, see Parallel Programming in .NET.
Взаимоблокировки и состояние гонки Deadlocks and race conditions
Многопоточность позволяет решить проблемы с пропускной способностью и скоростью реагирования, но при этом возникают новые проблемы: взаимоблокировки и конфликты. Multithreading solves problems with throughput and responsiveness, but in doing so it introduces new problems: deadlocks and race conditions.
Взаимоблокировки Deadlocks
Взаимоблокировка происходит, когда каждый из двух потоков пытается заблокировать ресурс, уже заблокированный другим потоком. A deadlock occurs when each of two threads tries to lock a resource the other has already locked. Ни один из потоков не может продолжить работу. Neither thread can make any further progress.
Многие методы классов управляемых потоков предоставляют значения времени ожидания для обнаружения взаимоблокировок. Many methods of the managed threading classes provide time-outs to help you detect deadlocks. Например, следующий код пытается получить блокировку для объекта с именем lockObject . For example, the following code attempts to acquire a lock on an object named lockObject . Если блокировка не будет получена в течение 300 миллисекунд, Monitor.TryEnter возвратит false . If the lock is not obtained in 300 milliseconds, Monitor.TryEnter returns false .
Состояние гонки Race conditions
Конфликт — это ошибка, которая возникает, когда результат программы зависит от того, какой из двух или более потоков первым достигнет определенного блока кода. A race condition is a bug that occurs when the outcome of a program depends on which of two or more threads reaches a particular block of code first. Выполнение программы часто дает различные результаты, и предсказать результат выполнения конкретного запуска невозможно. Running the program many times produces different results, and the result of any given run cannot be predicted.
Простой пример состояния гонки — увеличение поля. A simple example of a race condition is incrementing a field. Предположим, что класс содержит закрытое поле static (Shared в Visual Basic), которое увеличивается всякий раз при создании класса с помощью кода, например objCt++; (в C#) или objCt += 1 (в Visual Basic). Suppose a class has a private static field (Shared in Visual Basic) that is incremented every time an instance of the class is created, using code such as objCt++; (C#) or objCt += 1 (Visual Basic). Для этой операции необходимо загрузить значение из objCt в регистр, увеличить или уменьшить это значение и сохранить его в objCt . This operation requires loading the value from objCt into a register, incrementing the value, and storing it in objCt .
В многопоточных приложениях поток, загружающий и увеличивающий значение, может быть вытеснен другим потоком, который выполняет все три эти действия; если первый поток возобновляет выполнение и сохраняет его значение, он переопределяет objCt , не принимая во внимание тот факт, что в промежутке значение изменилось. In a multithreaded application, a thread that has loaded and incremented the value might be preempted by another thread which performs all three steps; when the first thread resumes execution and stores its value, it overwrites objCt without taking into account the fact that the value has changed in the interim.
Конкретно этого состояния гонки можно легко избежать, применяя методы класса Interlocked, например Interlocked.Increment. This particular race condition is easily avoided by using methods of the Interlocked class, such as Interlocked.Increment. Сведения о других технологиях синхронизации данных между несколькими потоками см. в разделе Синхронизация данных для многопоточности. To read about other techniques for synchronizing data among multiple threads, see Synchronizing Data for Multithreading.
Конфликты могут также возникать при синхронизации действий различных потоков. Race conditions can also occur when you synchronize the activities of multiple threads. При написании каждой строки кода необходимо учитывать, что может произойти, если поток будет вытеснен другим потоком до ее выполнения (или до одной из индивидуальных машинных команд, составляющих эту строку). Whenever you write a line of code, you must consider what might happen if a thread were preempted before executing the line (or before any of the individual machine instructions that make up the line), and another thread overtook it.
Статические члены и статические конструкторы Static members and static constructors
Класс не инициализируется, пока не завершится выполнение его конструктора (конструктор static в C# Shared Sub New в Visual Basic). A class is not initialized until its class constructor ( static constructor in C#, Shared Sub New in Visual Basic) has finished running. Чтобы предотвратить выполнение кода в еще не инициализированном типе, CLR блокирует все вызовы из других потоков для членов класса static (члены Shared в Visual Basic) до тех пор, пока выполнение конструктора класса не будет завершено. To prevent the execution of code on a type that is not initialized, the common language runtime blocks all calls from other threads to static members of the class ( Shared members in Visual Basic) until the class constructor has finished running.
Например, если конструктор класса запускает новый поток, а процедура потока вызывает член static класса, новый поток блокируется до завершения конструктора класса. For example, if a class constructor starts a new thread, and the thread procedure calls a static member of the class, the new thread blocks until the class constructor completes.
Это относится к любому типу, который может иметь конструктор static . This applies to any type that can have a static constructor.
Число процессоров Number of processors
Наличие нескольких процессоров или только одного процессора в системе может повлиять на многопоточную архитектуру. Whether there are multiple processors or only one processor available on a system can influence multithreaded architecture. Дополнительные сведения см. в разделе Количество процессоров. For more information, see Number of Processors.
Используйте свойство Environment.ProcessorCount, чтобы определить количество процессоров, доступных во время выполнения. Use the Environment.ProcessorCount property to determine the number of processors available at run time.
Основные рекомендации General recommendations
При использовании нескольких потоков соблюдайте следующие рекомендации: Consider the following guidelines when using multiple threads:
Не используйте Thread.Abort для завершения других потоков. Don’t use Thread.Abort to terminate other threads. Вызов метода Abort для другого потока аналогичен вызову исключения в этом потоке, когда неизвестно, на каком этапе находится обработка этого потока. Calling Abort on another thread is akin to throwing an exception on that thread, without knowing what point that thread has reached in its processing.
Не используйте Thread.Suspend и Thread.Resume для синхронизации действий между потоками. Don’t use Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads. Используйте вместо этого Mutex, ManualResetEvent, AutoResetEvent и Monitor. Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.
Не контролируйте выполнение рабочих потоков из основной программы (например, с помощью событий). Don’t control the execution of worker threads from your main program (using events, for example). Вместо этого составьте программу так, чтобы рабочие потоки ожидали доступности задания, выполняли его и оповещали другие части программы о его завершении. Instead, design your program so that worker threads are responsible for waiting until work is available, executing it, and notifying other parts of your program when finished. Если рабочие потоки не блокируются, можно использовать потоки из пула потоков. If your worker threads do not block, consider using thread pool threads. Monitor.PulseAll можно использовать в ситуациях, когда рабочие потоки блокируются. Monitor.PulseAll is useful in situations where worker threads block.
Не используйте типы как объекты блокировки. Don’t use types as lock objects. Это означает, что следует избегать кода lock(typeof(X)) в C# или SyncLock(GetType(X)) в Visual Basic, а также использования Monitor.Enter с объектами Type. That is, avoid code such as lock(typeof(X)) in C# or SyncLock(GetType(X)) in Visual Basic, or the use of Monitor.Enter with Type objects. Для каждого конкретного типа существует только один экземпляр System.Type в каждом домене приложения. For a given type, there is only one instance of System.Type per application domain. Если блокируемый тип является открытым, его может заблокировать чужой код, вызвав тем самым взаимоблокировку. If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks. Дополнительные вопросы см. Рекомендации по обеспечению надежности. For additional issues, see Reliability Best Practices.
Будьте внимательны при блокировке экземпляров, например lock(this) в C# или SyncLock(Me) в Visual Basic. Use caution when locking on instances, for example lock(this) in C# or SyncLock(Me) in Visual Basic. Если другой код в приложении, который является внешним для типа, заблокирует объект, может возникнуть взаимоблокировка. If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.
Следите за тем, чтобы каждый поток, который входит в монитор, обязательно вышел из этого монитора, даже если за время, пока поток находится в мониторе, возникает исключение. Do ensure that a thread that has entered a monitor always leaves that monitor, even if an exception occurs while the thread is in the monitor. Оператор C# lock и оператор Visual Basic SyncLock делают это автоматически, обеспечивая вызов метода Monitor.Exit с помощью блока finally. The C# lock statement and the Visual Basic SyncLock statement provide this behavior automatically, employing a finally block to ensure that Monitor.Exit is called. Если вы не можете проконтролировать вызов метода Exit, включите в свое приложение мьютекс. If you cannot ensure that Exit will be called, consider changing your design to use Mutex. Мьютекс автоматически освобождается, как только прекращается выполнение владеющего им потока. A mutex is automatically released when the thread that currently owns it terminates.
Для задач, которые требуют различных ресурсов, используйте несколько потоков и старайтесь не назначать несколько потоков одному ресурсу. Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. Например, любая задача с использованием ввода-вывода выигрывает от наличия собственного потока, поскольку во время операций ввода-вывода этот поток блокируется и, таким образом, разрешает выполнение других потоков. For example, any task involving I/O benefits from having its own thread, because that thread will block during I/O operations and thus allow other threads to execute. Входные данные пользователя — еще один ресурс, которому пойдет на пользу выделенный поток. User input is another resource that benefits from a dedicated thread. На однопроцессорном компьютере задача, требующая активных вычислений, сосуществует с входными данными пользователя и задачами, которые предусматривают операции ввода-вывода, однако несколько ресурсоемких задач могут конкурировать друг с другом. On a single-processor computer, a task that involves intensive computation coexists with user input and with tasks that involve I/O, but multiple computation-intensive tasks contend with each other.
Вместо оператора lock ( SyncLock в Visual Basic) для простого изменения состояния лучше использовать методы класса Interlocked. Consider using methods of the Interlocked class for simple state changes, instead of using the lock statement ( SyncLock in Visual Basic). Оператор lock — хороший универсальный инструмент, но класс Interlocked обеспечивает высокую производительность для обновлений, которые должны быть атомарными. The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. Если конкуренции нет, он выполняет внутри единственный префикс lock. Internally, it executes a single lock prefix if there is no contention. При проверке кода ищите код, похожий на показанный в следующих примерах. In code reviews, watch for code like that shown in the following examples. В первом примере увеличивается переменная состояния: In the first example, a state variable is incremented:
Вы можете повысить производительность, применяя метод Increment вместо оператора lock , как показано ниже. You can improve performance by using the Increment method instead of the lock statement, as follows:
Используйте метод Add для атомарных приращений более 1. Use the Add method for atomic increments larger than 1.
Во втором примере переменная ссылочного типа обновляется только в том случае, если она является пустой ссылкой ( Nothing в Visual Basic). In the second example, a reference type variable is updated only if it is a null reference ( Nothing in Visual Basic).
Чтобы повысить производительность, применяйте вместо этого метод CompareExchange, как показано ниже. Performance can be improved by using the CompareExchange method instead, as follows:
Перегрузка метода CompareExchange (T, T, T) предоставляет типобезопасную альтернативу для ссылочных типов. The CompareExchange (T, T, T) method overload provides a type-safe alternative for reference types.
Рекомендации для библиотек классов Recommendations for class libraries
При разработке библиотек классов для многопоточности необходимо учитывать следующие рекомендации. Consider the following guidelines when designing class libraries for multithreading:
Старайтесь не создавать потребность в синхронизации. Avoid the need for synchronization, if possible. Особенно это относится к коду, который используется наиболее часто. This is especially true for heavily used code. Например, алгоритм можно скорректировать таким образом, чтобы он допускал конфликты, а не устранял их. For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. Ненужная синхронизация снижает производительность и может привести к взаимоблокировке и конфликтам. Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.
Сделайте статические данные ( Shared в Visual Basic) по умолчанию потокобезопасными. Make static data ( Shared in Visual Basic) thread safe by default.
Данные экземпляров не должны быть потокобезопасными по умолчанию. Do not make instance data thread safe by default. Добавление блокировок для создания потокобезопасного кода снижает производительность, увеличивает конфликт блокировки и создает условия для возникновения взаимоблокировок. Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks to occur. В обычных моделях приложений пользовательский код одновременно выполняется только одним потоком, что уменьшает необходимость потокобезопасности. In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. По этой причине библиотеки классов .NET не являются потокобезопасными по умолчанию. For this reason, the .NET class libraries are not thread safe by default.
Не предоставляйте статические методы, изменяющие статическое состояние. Avoid providing static methods that alter static state. В обычных сценариях сервера статическое состояние используется запросами совместно, а значит, код одновременно могут выполнять сразу несколько потоков. In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. Это открывает возможность для появления потоковых ошибок. This opens up the possibility of threading bugs. Попробуйте применить конструктивный шаблон, инкапсулирующий данные в экземпляры, которые не являются общими для запросов. Consider using a design pattern that encapsulates data into instances that are not shared across requests. Кроме того, если статические данные синхронизируются, вызовы между статическими методами, изменяющие состояние, могут приводить к взаимоблокировкам или избыточной синхронизации, что, в свою очередь, снижает производительность. Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.