Sobes.tech
Middle+

¿Cómo se maneja el trabajo con hilos y tareas en C# cuando varios hilos acceden a un estado compartido?

sobes.tech AI

Respuesta de la IA

Al trabajar con un estado compartido por varios hilos en C#, surgen problemas de sincronización y posibles condiciones de carrera. Para garantizar la seguridad en los hilos, se utilizan los siguientes mecanismos:

  1. Bloqueo (Locking):

    • Se utiliza el operador lock para crear una sección crítica que garantiza que solo un hilo pueda acceder al recurso protegido a la vez.
    • Requiere un objeto de marcador de posición (generalmente private readonly object) que se usa para la sincronización.
    // Ejemplo de uso de lock
    private readonly object _lockObject = new object();
    private int _sharedCounter = 0;
    
    public void IncrementCounter()
    {
        lock (_lockObject)
        {
            _sharedCounter++; // Sección crítica
        }
    }
    
  2. Operaciones atómicas:

    • La clase System.Threading.Interlocked proporciona operaciones atómicas (por ejemplo, Increment, Decrement, Add, Exchange) que son seguras para los hilos y no requieren bloqueo explícito para realizar una operación.
    // Ejemplo de uso de Interlocked
    private volatile int _sharedValue = 0; // volatile para visibilidad de cambios
    
    public void UpdateValue(int newValue)
    {
        Interlocked.Exchange(ref _sharedValue, newValue); // Asignación atómica
    }
    
    public void IncrementValue()
    {
         Interlocked.Increment(ref _sharedValue); // Incremento atómico
    }
    
    • Aunque volatile garantiza la visibilidad de los cambios entre hilos, no asegura la atomicidad de operaciones de múltiples pasos. Para operaciones atómicas, se debe usar Interlocked.
  3. Clases de sincronización de System.Threading y System.Threading.Tasks:

    • Monitor: mecanismo de bloqueo de nivel inferior en comparación con lock.
    • Mutex: permite sincronizar el acceso a un recurso entre varios procesos.
    • SemaphoreSlim: limita la cantidad de hilos que pueden acceder a un recurso simultáneamente.
    • ReaderWriterLockSlim: permite que múltiples hilos lean un recurso simultáneamente, pero solo un hilo pueda escribir.
  4. Colecciones seguras para hilos:

    • El espacio de nombres System.Collections.Concurrent contiene colecciones (ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>) diseñadas específicamente para acceso concurrente. El uso de estas colecciones elimina la necesidad de sincronización manual en operaciones básicas con la colección.
    // Ejemplo de uso de ConcurrentDictionary
    private ConcurrentDictionary<string, int> _sharedData = new ConcurrentDictionary<string, int>();
    
    public void AddOrUpdateData(string key, int value)
    {
        _sharedData.AddOrUpdate(key, value, (k, oldValue) => oldValue + value);
    }
    
  5. Objetos inmutables:

    • Crear objetos inmutables significa que, una vez creados, su estado no puede cambiar. Si el estado compartido se representa mediante un objeto inmutable, múltiples hilos pueden leerlo de forma segura sin necesidad de sincronización. La actualización del estado implica crear una nueva instancia del objeto.

Al trabajar con tareas (Task) en TPL (Task Parallel Library), también es necesario considerar la sincronización al acceder al estado compartido. TPL facilita la creación y gestión de operaciones paralelas, pero no elimina la responsabilidad de garantizar la seguridad en los hilos al trabajar con datos compartidos.

La elección del mecanismo de sincronización adecuado depende de la tarea específica y de las características del acceso al estado compartido.