Como funciona o trabalho com threads e tarefas em C# quando vários threads acessam um estado comum?
sobes.tech IA
Resposta da IA
Ao trabalhar com um estado compartilhado por vários threads em C#, surgem problemas de sincronização e condições de corrida. Para garantir a segurança dos threads, utilizam-se os seguintes mecanismos:
-
Bloqueio (Locking):
- Utiliza-se o operador
lockpara criar uma secção crítica que garante que apenas um thread possa aceder ao recurso protegido de cada vez. - Requer um objeto de bloqueio (geralmente
private readonly object) usado para sincronização.
// Exemplo de uso de lock private readonly object _lockObject = new object(); private int _sharedCounter = 0; public void IncrementCounter() { lock (_lockObject) { _sharedCounter++; // Secção crítica } } - Utiliza-se o operador
-
Operações atómicas:
- A classe
System.Threading.Interlockedfornece operações atómicas (por exemplo,Increment,Decrement,Add,Exchange) que são seguras para os threads e não requerem bloqueio explícito para realizar uma operação.
// Exemplo de uso de Interlocked private volatile int _sharedValue = 0; // volatile para visibilidade de alterações public void UpdateValue(int newValue) { Interlocked.Exchange(ref _sharedValue, newValue); // Atribuição atómica } public void IncrementValue() { Interlocked.Increment(ref _sharedValue); // Aumento atómico }- Embora
volatilegaranta a visibilidade das alterações entre threads, não garante a atomicidade de operações de múltiplos passos. Para operações atómicas, deve-se usarInterlocked.
- A classe
-
Classes de sincronização de
System.ThreadingeSystem.Threading.Tasks:Monitor: mecanismo de bloqueio de baixo nível em comparação comlock.Mutex: permite sincronizar o acesso a um recurso entre vários processos.SemaphoreSlim: limita o número de threads que podem aceder a um recurso simultaneamente.ReaderWriterLockSlim: permite que múltiplos threads leiam um recurso simultaneamente, mas apenas um pode escrever.
-
Coleções seguras para threads:
- O espaço de nomes
System.Collections.Concurrentcontém coleções (ConcurrentBag<T>,ConcurrentDictionary<TKey, TValue>,ConcurrentQueue<T>,ConcurrentStack<T>) projetadas especificamente para acesso concorrente. O uso destas coleções elimina a necessidade de sincronização manual em operações básicas com a coleção.
// Exemplo 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); } - O espaço de nomes
-
Objetos imutáveis:
- Criar objetos imutáveis significa que, uma vez criados, o seu estado não pode ser alterado. Se o estado compartilhado é representado por um objeto imutável, múltiplos threads podem lê-lo com segurança sem necessidade de sincronização. A atualização do estado consiste em criar uma nova instância do objeto.
Ao trabalhar com tarefas (Task) na TPL (Task Parallel Library), também é necessário considerar a sincronização ao aceder ao estado compartilhado. A TPL facilita a criação e gestão de operações paralelas, mas não elimina a responsabilidade de garantir a segurança dos threads ao trabalhar com dados partilhados.
A escolha do mecanismo de sincronização adequado depende da tarefa específica e das características do acesso ao estado compartilhado.