Procedimientos recomendados para el subprocesamiento administradoManaged threading best practices

El multithreading requiere que la programación sea cuidadosa.Multithreading requires careful programming. La complejidad de muchas tareas se puede reducir poniendo las solicitudes de ejecución en cola por subprocesos del grupo de subprocesos.For most tasks, you can reduce complexity by queuing requests for execution by thread pool threads. En este tema se tratan situaciones más complicadas, como coordinar el trabajo de múltiples subprocesos, o controlar los subprocesos que se bloquean.This topic addresses more difficult situations, such as coordinating the work of multiple threads, or handling threads that block.

Nota

A partir de NET Framework 4, la biblioteca TPL y PLINQ proporcionan API que reducen parte de la complejidad y los riesgos que entraña la programación multiproceso.Starting with the .NET Framework 4, the Task Parallel Library and PLINQ provide APIs that reduce some of the complexity and risks of multi-threaded programming. Para más información, consulte Programación en paralelo en .NET.For more information, see Parallel Programming in .NET.

Interbloqueos y condiciones de carreraDeadlocks and race conditions

El multithreading resuelve problemas de rendimiento y de capacidad de respuesta, pero al hacerlo también crea nuevos problemas, como interbloqueos y condiciones de carrera.Multithreading solves problems with throughput and responsiveness, but in doing so it introduces new problems: deadlocks and race conditions.

InterbloqueosDeadlocks

Un interbloqueo tiene lugar cuando dos subprocesos intentan bloquear un recurso que ya ha bloqueado uno de estos subprocesos.A deadlock occurs when each of two threads tries to lock a resource the other has already locked. Ninguno de los subprocesos puede avanzar.Neither thread can make any further progress.

Muchos métodos de las clases del subprocesamiento administrado ofrecen tiempos de espera que se utilizan para detectar interbloqueos.Many methods of the managed threading classes provide time-outs to help you detect deadlocks. Por ejemplo, con el siguiente código se intenta obtener un bloqueo en un objeto llamado lockObject.For example, the following code attempts to acquire a lock on an object named lockObject. Si el bloqueo no se consigue en 300 milisegundos, Monitor.TryEnter devuelve el valor false.If the lock is not obtained in 300 milliseconds, Monitor.TryEnter returns 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.  
}  

Condiciones de carreraRace conditions

Una condición de carrera es un error que se produce cuando el resultado de un programa depende del primero de dos o más subprocesos que consiga llegar hasta un bloque específico de código.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. Ejecutar el programa muchas veces genera distintos resultados y no es posible predecir el resultado de una ejecución específica.Running the program many times produces different results, and the result of any given run cannot be predicted.

Un ejemplo sencillo de una condición de carrera es el incremento de un campo.A simple example of a race condition is incrementing a field. Suponga una clase que tiene un campo privado static (Shared en Visual Basic) que se incrementa cada vez que se crea una instancia de la clase, mediante código como objCt++; (C#) o 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). Esta operación requiere cargar el valor de objCt en un registro, incrementar el valor y almacenarlo en objCt.This operation requires loading the value from objCt into a register, incrementing the value, and storing it in objCt.

En una aplicación multiproceso, un subproceso que realiza los tres pasos puede adelantar al subproceso que ha cargado e incrementado el valor; cuando el primer subproceso reanuda la ejecución y almacena su valor, sobrescribe objCt sin tener en cuenta el hecho de que el valor ha cambiado mientras tanto.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.

Para evitar fácilmente esta condición de carrera determinada, utilice los métodos de la clase Interlocked, como Interlocked.Increment.This particular race condition is easily avoided by using methods of the Interlocked class, such as Interlocked.Increment. Para más información sobre otras técnicas de sincronización de datos entre varios subprocesos, consulte Sincronización de datos para multithreading.To read about other techniques for synchronizing data among multiple threads, see Synchronizing Data for Multithreading.

También se pueden producir condiciones de carrera al sincronizar las actividades de varios subprocesos.Race conditions can also occur when you synchronize the activities of multiple threads. Siempre que escriba una línea de código, debe tener en cuenta qué puede ocurrir si otro subproceso adelanta a un subproceso antes de ejecutar la línea (o antes de cualquiera de las instrucciones máquina que forman la línea).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.

Miembros estáticos y constructores estáticosStatic members and static constructors

No se inicializa una clase hasta que su constructor de clase (constructorstatic en C#, Shared Sub New en Visual Basic) haya terminado de ejecutarse.A class is not initialized until its class constructor (static constructor in C#, Shared Sub New in Visual Basic) has finished running. Para evitar la ejecución de código en un tipo no inicializado, Common Language Runtime bloquea todas las llamadas de otros subprocesos a los miembros static de la clase (miembrosShared en Visual Basic) hasta que el constructor de clase termina de ejecutarse.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.

Por ejemplo, si un constructor de clase inicia un nuevo subproceso, y el procedimiento del subproceso llama a un miembro static de la clase, el nuevo subproceso se bloquea hasta que el constructor de clase finalice.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.

Esto se aplica a cualquier tipo que pueda tener un constructor static.This applies to any type that can have a static constructor.

Número de procesadoresNumber of processors

Si hay varios procesadores o uno solo disponibles en un sistema puede influir en la arquitectura de multiproceso.Whether there are multiple processors or only one processor available on a system can influence multithreaded architecture. Para obtener más información, vea Número de procesadores.For more information, see Number of Processors.

Use la propiedad Environment.ProcessorCount para determinar el número de procesadores disponibles en tiempo de ejecución.Use the Environment.ProcessorCount property to determine the number of processors available at runtime.

Recomendaciones generalesGeneral recommendations

Tenga en cuenta las siguientes instrucciones cuando utilice varios subprocesos:Consider the following guidelines when using multiple threads:

  • No utilice Thread.Abort para finalizar otros subprocesos.Don't use Thread.Abort to terminate other threads. Una llamada a Abort en otro subproceso es similar a iniciar una excepción en ese subproceso, sin conocer qué punto ha alcanzado en su procesamiento.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.

  • No utilice Thread.Suspend ni Thread.Resume para sincronizar las actividades de varios subprocesos.Don't use Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads. Utilice Mutex, ManualResetEvent, AutoResetEvent y Monitor.Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.

  • No controle la ejecución de subprocesos de trabajo desde el programa principal (con eventos, por ejemplo).Don't control the execution of worker threads from your main program (using events, for example). En su lugar, diseñe un programa de forma que los subprocesos de trabajo sean los que tengan que esperar hasta que haya trabajo disponible, lo ejecuten y notifiquen su finalización a otras partes del programa.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. Si los subprocesos de trabajo no se bloquean, puede ser conveniente usar subprocesos del grupo de subprocesos.If your worker threads do not block, consider using thread pool threads. Monitor.PulseAll resulta útil en aquellas situaciones en las que los subprocesos de trabajo se bloquean.Monitor.PulseAll is useful in situations where worker threads block.

  • No utilice los tipos como objetos de bloqueo.Don't use types as lock objects. Es decir, evite código como lock(typeof(X)) en C# o SyncLock(GetType(X)) en Visual Basic o el uso de Monitor.Enter con objetos 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. Para un tipo determinado, hay sólo una instancia de System.Type por el dominio de aplicación.For a given type, there is only one instance of System.Type per application domain. Si el tipo que quiere bloquear es público, codifique uno que su tipo no pueda bloquear, para evitar interbloqueos.If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks. Para otros problemas, consulte Procedimientos recomendados para la confiabilidad.For additional issues, see Reliability Best Practices.

  • Tenga cuidado al efectuar bloqueos en instancias, por ejemplo lock(this) en C# o SyncLock(Me) en Visual Basic.Use caution when locking on instances, for example lock(this) in C# or SyncLock(Me) in Visual Basic. Si otra parte del código de la aplicación, ajeno al tipo, bloquea el objeto, podrían producirse interbloqueos.If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.

  • Asegúrese de que un subproceso que entra en un monitor siempre sale de ese monitor, aun en el caso de que se produzca una excepción mientras el subproceso se encuentra en el monitor.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. La instrucción lock de C# y la instrucción SyncLock de Visual Basic ofrecen automáticamente este comportamiento mediante un bloque finally que garantiza la llamada a Monitor.Exit.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. Si no está seguro de que se llamará a Exit, considere la posibilidad de cambiar el diseño con el fin de usar Mutex.If you cannot ensure that Exit will be called, consider changing your design to use Mutex. Una zona de exclusión mutua se libera automáticamente cuando finaliza el subproceso al que pertenece.A mutex is automatically released when the thread that currently owns it terminates.

  • Utilice varios subprocesos para tareas que requieren recursos diferentes, y evite asignar varios subprocesos a un solo recurso.Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. Por ejemplo, en tareas que impliquen beneficios de E/S por tener un subproceso propio, ya que ese subproceso se bloquea durante las operaciones de E/S y, de este modo, permite ejecutar otros subprocesos.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. Los datos proporcionados por el usuario son otro recurso que se beneficia de la utilización de un subproceso dedicado.User input is another resource that benefits from a dedicated thread. En un equipo de un solo procesador, una tarea que implica un cálculo intensivo coexiste con los datos proporcionados por el usuario y con tareas que implican la E/S, pero varias tareas de cálculo intensivo compiten entre ellas.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.

  • Considere la posibilidad de utilizar métodos de la clase Interlocked para los cambios de estado simples, en lugar de utilizar la instrucción lock (SyncLock en Visual Basic).Consider using methods of the Interlocked class for simple state changes, instead of using the lock statement (SyncLock in Visual Basic). La instrucción lock es una buena herramienta de uso general, pero la clase Interlocked genera mejor rendimiento para las actualizaciones que deben ser atómicas.The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. Internamente, ejecuta un solo prefijo de bloqueo si no hay contención.Internally, it executes a single lock prefix if there is no contention. En las revisiones de código, inspeccione código similar al que se muestra en los ejemplos siguientes.In code reviews, watch for code like that shown in the following examples. En el primer ejemplo, se incrementa una variable de estado:In the first example, a state variable is incremented:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)   
    {  
        myField++;  
    }  
    

    Para mejorar el rendimiento, utilice el método Increment en lugar de la instrucción lock, de la siguiente manera:You can improve performance by using the Increment method instead of the lock statement, as follows:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Nota

    En .NET Framework 2.0 y versiones posteriores, use el método Add para incrementos atómicos mayores que 1.In the .NET Framework 2.0 and later, use the Add method for atomic increments larger than 1.

    En el segundo ejemplo, se actualiza una variable de tipo de referencia sólo si es una referencia nula (Nothing en Visual Basic).In the second example, a reference type variable is updated only if it is a null reference (Nothing in 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;
        }  
    }  
    

    Se puede mejorar el rendimiento utilizando el método CompareExchange de la siguiente manera:Performance can be improved by using the CompareExchange method instead, as follows:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Nota

    A partir de .NET Framework 2.0, la sobrecarga del método CompareExchange<T>(T, T, T) proporciona una alternativa de seguridad de tipos para tipos de referencia.Beginning with .NET Framework 2.0, the CompareExchange<T>(T, T, T) method overload provides a type-safe alternative for reference types.

Recomendaciones para las bibliotecas de clasesRecommendations for class libraries

Tenga en cuenta las instrucciones siguientes cuando diseñe bibliotecas de clases para el multithreading:Consider the following guidelines when designing class libraries for multithreading:

  • Evite la necesidad de sincronización si es posible.Avoid the need for synchronization, if possible. Esto se aplica especialmente en el caso de código muy utilizado.This is especially true for heavily used code. Por ejemplo, se podría ajustar un algoritmo de modo que tolere una condición de carrera en lugar de eliminarla.For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. La sincronización innecesaria disminuye el rendimiento y crea la posibilidad de interbloqueos y condiciones de carrera.Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.

  • Procure que los datos estáticos (Shared en Visual Basic) sean seguros para subprocesos de manera predeterminada.Make static data (Shared in Visual Basic) thread safe by default.

  • No convierta los datos de instancia en datos seguros para subprocesos de manera predeterminada.Do not make instance data thread safe by default. Al agregar bloqueos para crear código seguro para subprocesos, se reduce el rendimiento, se incrementa la contención de bloqueos y se crea la posibilidad de que se produzcan interbloqueos.Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks to occur. En los modelos de aplicación comunes, sólo un subproceso a la vez ejecuta código de usuario, lo que minimiza la necesidad de la seguridad para subprocesos.In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. Por esta razón, las bibliotecas de clases de .NET Framework no son seguras para subprocesos de forma predeterminada.For this reason, the .NET Framework class libraries are not thread safe by default.

  • Evite proporcionar métodos estáticos que alteren el estado estático.Avoid providing static methods that alter static state. En escenarios de servidor comunes, el estado estático se comparte entre las solicitudes, lo que significa que varios subprocesos pueden ejecutar a la vez ese código.In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. De este modo, se abre la posibilidad de errores de subprocesos.This opens up the possibility of threading bugs. Considere la posibilidad de utilizar un modelo de diseño que encapsule los datos en instancias no compartidas por las solicitudes.Consider using a design pattern that encapsulates data into instances that are not shared across requests. Además, si se sincronizan los datos estáticos, las llamadas entre los métodos estáticos que modifican el estado pueden generar interbloqueos o sincronización redundante, lo que afecta negativamente al rendimiento.Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.

Vea tambiénSee also