Partilhar via


Multithreading: Como usar as classes de sincronização MFC

A sincronização do acesso a recursos entre threads é um problema comum ao escrever aplicativos multithreaded. Ter dois ou mais threads acessando simultaneamente os mesmos dados pode levar a resultados indesejáveis e imprevisíveis. Por exemplo, um thread pode estar atualizando o conteúdo de uma estrutura enquanto outro thread está lendo o conteúdo da mesma estrutura. Não se sabe quais dados o segmento de leitura receberá: os dados antigos, os dados recém-gravados ou, possivelmente, uma mistura de ambos. MFC fornece várias classes de sincronização e acesso à sincronização para ajudar na resolução deste problema. Este tópico explica as classes disponíveis e como usá-las para criar classes thread-safe em um aplicativo multithreaded típico.

Um aplicativo multithreaded típico tem uma classe que representa um recurso a ser compartilhado entre threads. Uma classe projetada corretamente e totalmente segura para threads não requer que você chame nenhuma função de sincronização. Tudo é tratado internamente para a classe, permitindo que você se concentre em como usar melhor a classe, não em como ela pode ser corrompida. Uma técnica eficaz para criar uma classe totalmente thread-safe é mesclar a classe de sincronização na classe de recurso. Mesclar as classes de sincronização na classe compartilhada é um processo simples.

Como exemplo, pegue um aplicativo que mantém uma lista vinculada de contas. Esta aplicação permite que até três contas sejam examinadas em janelas separadas, mas apenas uma pode ser atualizada a qualquer momento. Quando uma conta é atualizada, os dados atualizados são enviados pela rede para um arquivo de dados.

Este aplicativo de exemplo usa todos os três tipos de classes de sincronização. Como ele permite que até três contas sejam examinadas ao mesmo tempo, ele usa CSemaphore para limitar o acesso a três objetos de exibição. Quando ocorre uma tentativa de exibir uma quarta conta, o aplicativo aguarda até que uma das três primeiras janelas seja fechada ou falha. Quando uma conta é atualizada, o aplicativo usa CCriticalSection para garantir que apenas uma conta seja atualizada de cada vez. Depois que a atualização for bem-sucedida, ele sinaliza CEvent, que libera um thread aguardando que o evento seja sinalizado. Este thread envia os novos dados para o arquivo de dados.

Projetando uma classe Thread-Safe

Para tornar uma classe totalmente thread-safe, primeiro adicione a classe de sincronização apropriada às classes compartilhadas como um membro de dados. No exemplo anterior de gerenciamento de contas, um CSemaphore membro de dados seria adicionado à classe de exibição, um CCriticalSection membro de dados seria adicionado à classe de lista vinculada e um CEvent membro de dados seria adicionado à classe de armazenamento de dados.

Em seguida, adicione chamadas de sincronização a todas as funções de membro que modificam os dados na classe ou acessam um recurso controlado. Em cada função, você deve criar um objeto CSingleLock ou CMultiLock e chamar a função desse Lock objeto. Quando o objeto de bloqueio sai do escopo e é destruído, o destruidor do objeto chama Unlock por você, liberando o recurso. Claro, você pode ligar Unlock diretamente se quiser.

Projetar sua classe thread-safe dessa maneira permite que ela seja usada em um aplicativo multithreaded tão facilmente quanto uma classe não thread-safe, mas com um nível mais alto de segurança. Encapsular o objeto de sincronização e o objeto de acesso à sincronização na classe do recurso oferece todos os benefícios da programação completamente segura em threads, sem a desvantagem de manter o código de sincronização.

O exemplo de código a seguir demonstra esse método usando um membro de dados, m_CritSection (do tipo CCriticalSection), declarado na classe de recurso compartilhado e um CSingleLock objeto. A sincronização do recurso compartilhado (derivado de CWinThread) é tentada criando um CSingleLock objeto usando o endereço do m_CritSection objeto. Faz-se uma tentativa de bloquear o recurso e, após obtê-lo, realiza-se o trabalho no objeto partilhado. Quando o trabalho é concluído, o recurso é desbloqueado com uma chamada para Unlock.

CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...

singleLock.Unlock();

Observação

CCriticalSection, ao contrário de outras classes de sincronização MFC, não tem a opção de uma solicitação de bloqueio cronometrado. O período de espera para que um fio se torne livre é infinito.

As desvantagens dessa abordagem são que a classe será um pouco mais lenta do que a mesma classe sem os objetos de sincronização adicionados. Além disso, se houver uma chance de que mais de um thread possa excluir o objeto, a abordagem mesclada nem sempre funciona. Nessa situação, é melhor manter objetos de sincronização separados.

Para obter informações sobre como determinar qual classe de sincronização usar em diferentes situações, consulte Multithreading: Quando usar as classes de sincronização. Para obter mais informações sobre sincronização, consulte Sincronização no SDK do Windows. Para obter mais informações sobre o suporte a multithreading no MFC, consulte Multithreading com C++ e MFC.

Ver também

Multithreading com C++ e MFC