Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O compilador Microsoft C/C++ (MSVC) fornece suporte para a criação de aplicativos multithread. Considere o uso de mais de um thread se seu aplicativo precisar executar operações caras que fariam com que a interface do usuário deixasse de responder.
Com o MSVC, há várias maneiras de programar com vários threads: você pode usar C++/WinRT e a biblioteca do Tempo de Execução do Windows, a biblioteca Microsoft Foundation Class (MFC), C++/CLI e o tempo de execução do .NET ou a biblioteca de tempo de execução C e a API do Win32. Este artigo é sobre multithreading em C. Para obter um código de exemplo, consulte Exemplo de programa multithread em C.
Programas multithread
Um thread é basicamente um caminho de execução através de um programa. É também a menor unidade de execução que o Win32 agenda. Um thread consiste em uma pilha, o estado dos registros da CPU e uma entrada na lista de execução do agendador do sistema. Cada thread compartilha todos os recursos do processo.
Um processo consiste em um ou mais threads e o código, dados e outros recursos de um programa na memória. Os recursos típicos do programa são arquivos abertos, semáforos e memória alocada dinamicamente. Um programa é executado quando o agendador do sistema dá a um de seus threads o controle de execução. O agendador determina quais threads devem ser executados e quando eles devem ser executados. Threads de prioridade mais baixa podem ter que esperar enquanto threads de prioridade mais alta concluem suas tarefas. Em máquinas multiprocessador, o agendador pode mover threads individuais para processadores diferentes para equilibrar a carga da CPU.
Cada thread em um processo opera de forma independente. A menos que você os torne visíveis uns para os outros, os threads são executados individualmente e não estão cientes dos outros threads em um processo. Threads que compartilham recursos comuns, no entanto, devem coordenar seu trabalho usando semáforos ou outro método de comunicação entre processos. Para obter mais informações sobre como sincronizar threads, consulte Escrevendo um programa Win32 multithreaded.
Suporte de biblioteca para multithreading
Todas as versões do CRT agora suportam multithreading, com exceção das versões sem bloqueio de algumas funções. Para obter mais informações, consulte Desempenho de bibliotecas multithreaded. Para obter informações sobre as versões do CRT disponíveis para vincular ao seu código, consulte Recursos da biblioteca CRT.
Incluir arquivos para multithreading
Os arquivos de inclusão CRT padrão declaram as funções da biblioteca de tempo de execução C à medida que são implementadas nas bibliotecas. Se as opções do compilador especificarem as convenções de chamada __fastcall ou __vectorcall , o compilador assumirá que todas as funções devem ser chamadas usando a convenção de chamada de registro. As funções de biblioteca de tempo de execução usam a convenção de chamada C, e as declarações nos ficheiros de inclusão padrão indicam ao compilador para que gerem referências externas corretas para essas funções.
Funções CRT para controle de rosca
Todos os programas Win32 têm pelo menos um thread. Qualquer thread é capaz de criar threads adicionais. Um thread pode concluir seu trabalho rapidamente e, em seguida, terminar, ou pode permanecer ativo durante a vida do programa.
As bibliotecas CRT fornecem as seguintes funções para criação e terminação de threads: _beginthread, _beginthreadex, _endthread e _endthreadex.
As _beginthread funções e _beginthreadex criam um novo thread e retornam um identificador de thread se a operação for bem-sucedida. O thread termina automaticamente se concluir a execução. Ou, ele pode se encerrar com uma chamada para _endthread ou _endthreadex.
Observação
Se você chamar rotinas de tempo de execução C de um programa criado com libcmt.lib, você deve iniciar seus threads com a _beginthread função ou _beginthreadex . Não use as funções Win32 ExitThread e CreateThread. O uso SuspendThread pode levar a um deadlock quando mais de um thread é bloqueado aguardando que o thread suspenso conclua seu acesso a uma estrutura de dados de tempo de execução C.
As funções _beginthread e _beginthreadex
As _beginthread funções e _beginthreadex criam um novo thread. Um thread compartilha o código e os segmentos de dados de um processo com outros threads no processo, mas tem seus próprios valores de registro exclusivos, espaço de pilha e endereço de instrução atual. O sistema dá tempo de CPU para cada thread, para que todos os threads em um processo possam ser executados simultaneamente.
_beginthread e _beginthreadex são semelhantes à função CreateThread na API do Win32, mas tem estas diferenças:
Eles inicializam certas variáveis da biblioteca de tempo de execução C. Isso é importante somente se você usar a biblioteca de tempo de execução C em seus threads.
CreateThreadAjuda a fornecer controle sobre os atributos de segurança. Você pode usar essa função para iniciar um thread em um estado suspenso.
_beginthread e _beginthreadex retorne um identificador para o novo thread se for bem-sucedido ou um código de erro se houver um erro.
As funções _endthread e _endthreadex
A função _endthread encerra um thread criado por _beginthread (e, da mesma forma, _endthreadex encerra um thread criado por _beginthreadex). Os threads terminam automaticamente quando terminam.
_endthread e _endthreadex são úteis para a terminação condicional dentro de um tópico. Um thread dedicado ao processamento de comunicações, por exemplo, pode fechar se não conseguir obter o controle da porta de comunicações.
Escrever um programa Win32 multitarefa
Quando você escreve um programa com vários threads, você deve coordenar seu comportamento e uso dos recursos do programa. Além disso, certifique-se de que cada thread recebe a sua própria pilha.
Partilha de recursos comuns entre threads
Observação
Para uma discussão semelhante do ponto de vista do MFC, consulte Multithreading: Dicas de programação e Multithreading: Quando usar as classes de sincronização.
Cada thread tem sua própria pilha e sua própria cópia dos registros da CPU. Outros recursos, como arquivos, dados estáticos e memória de pilha, são compartilhados por todos os threads no processo. Os threads que usam esses recursos comuns devem ser sincronizados. Win32 fornece várias maneiras de sincronizar recursos, incluindo semáforos, seções críticas, eventos e mutexes.
Quando vários threads estão acessando dados estáticos, seu programa deve fornecer possíveis conflitos de recursos. Considere um programa em que um thread atualiza uma estrutura de dados estática contendo coordenadas x,y para itens a serem exibidos por outro thread. Se a thread de atualização alterar a coordenada x e for interrompida antes de poder alterar a coordenada y, a thread de exibição poderá ser agendada antes que a coordenada y seja atualizada. O item seria exibido no local errado. Você pode evitar esse problema usando semáforos para controlar o acesso à estrutura.
Um mutex (abreviação de "exclusão mútua") é uma forma de comunicação entre threads ou processos que estão executados de forma assíncrona entre si. Essa comunicação pode ser usada para coordenar as atividades de vários threads ou processos, normalmente controlando o acesso a um recurso compartilhado bloqueando e desbloqueando o recurso. Para resolver este problema de atualização de coordenadas x,y, a thread de atualização define um mutex indicando que a estrutura de dados está em uso antes de executar a atualização. Eliminaria o mutex depois de ambas as coordenadas terem sido processadas. O thread de exibição deve aguardar que o mutex fique claro antes de atualizar o display. Este processo de espera por um mutex é muitas vezes chamado de bloqueio em um mutex, porque o processo é bloqueado e não pode continuar até que o mutex seja limpo.
O programa Bounce.c mostrado em Sample Multithread C Program usa um mutex nomeado ScreenMutex para coordenar atualizações de tela. Cada vez que um dos threads de exibição está pronto para gravar na tela, chama WaitForSingleObject com o identificador para ScreenMutex e a constante INFINITE para indicar que a chamada WaitForSingleObject deve bloquear o mutex e não deve expirar. Se ScreenMutex estiver livre, a funcionalidade de espera define o bloqueio mutex para que outros threads não possam interferir na exibição e continua executando o thread. Caso contrário, o thread bloqueia até que o mutex seja limpo. Quando o thread conclui a atualização de exibição, ele libera o mutex chamando ReleaseMutex.
Exibições de tela e dados estáticos são apenas dois dos recursos que exigem gerenciamento cuidadoso. Por exemplo, seu programa pode ter vários threads acessando o mesmo arquivo. Como pode acontecer que outra *thread* mova o ponteiro do ficheiro, cada *thread* deve reiniciar o ponteiro do ficheiro antes de ler ou gravar. Além disso, cada thread deve certificar-se de que não é antecipado entre o momento em que posiciona o ponteiro e o momento em que acessa o arquivo. Esses threads devem usar um semáforo para coordenar o acesso ao arquivo, colocando entre parênteses cada acesso ao arquivo com WaitForSingleObject e ReleaseMutex chamadas. O exemplo de código a seguir ilustra essa técnica:
HANDLE hIOMutex = CreateMutex (NULL, FALSE, NULL);
WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);
Pilhas de roscas
Todo o espaço de pilha padrão de um aplicativo é alocado para o primeiro thread de execução, que é conhecido como thread 1. Como resultado, deve especificar a quantidade de memória a alocar para cada stack separado para cada thread adicional que o seu programa precisa. O sistema operacional aloca espaço de pilha adicional para o thread, se necessário, mas você deve especificar um valor padrão.
O primeiro argumento na chamada _beginthread é um ponteiro para a função BounceProc, que executa as threads. O segundo argumento especifica o tamanho da pilha padrão para o thread. O último argumento é um número de ID que é passado para BounceProc.
BounceProc usa o número de ID para semear o gerador de números aleatórios e selecionar o atributo de cor e o caractere de exibição do thread.
As threads que fazem chamadas para a biblioteca de tempo de execução do C ou para a API do Win32 devem reservar espaço de pilha suficiente para a biblioteca e as funções da API que elas chamam. A função C printf requer mais de 500 bytes de espaço de pilha e você deve ter 2K bytes de espaço de pilha disponíveis ao chamar rotinas de API do Win32.
Como cada thread tem a sua própria pilha, é possível evitar colisões sobre itens de dados utilizando o mínimo de dados estáticos possível. Projete seu programa para usar variáveis de pilha automáticas para todos os dados que podem ser privados de um thread. As únicas variáveis globais no programa Bounce.c são mutexes ou variáveis que nunca mudam depois de serem inicializadas.
O Win32 também fornece armazenamento local de thread (TLS) para armazenar dados por thread. Para obter mais informações, consulte Armazenamento local de thread (TLS).
Evitando áreas problemáticas com programas multithread
Há vários problemas que você pode encontrar ao criar, vincular ou executar um programa C multithread. Alguns dos problemas mais comuns são descritos na tabela a seguir. (Para uma discussão semelhante do ponto de vista do MFC, consulte Multithreading: Dicas de programação.)
| Problema | Causa provável |
|---|---|
| Você recebe uma caixa de mensagem mostrando que seu programa causou uma violação de proteção. | Muitos erros de programação do Win32 causam violações de proteção. Uma causa comum de violações de proteção é a atribuição indireta de dados a ponteiros nulos. Como isso resulta em seu programa tentando acessar a memória que não pertence a ele, uma violação de proteção é emitida. Uma maneira fácil de detetar a causa de uma violação de proteção é compilar seu programa com informações de depuração e, em seguida, executá-lo através do depurador no ambiente do Visual Studio. Quando ocorre a falha de proteção, o Windows transfere o controle para o depurador e o cursor é posicionado na linha que causou o problema. |
| Seu programa gera inúmeros erros de compilação e link. | Você pode eliminar muitos problemas potenciais definindo o nível de aviso do compilador para um de seus valores mais altos e prestando atenção às mensagens de aviso. Usando as opções de nível de aviso de nível 3 ou nível 4, você pode detetar conversões de dados não intencionais, protótipos de função ausentes e uso de recursos não ANSI. |
Ver também
Suporte a multithreading para código mais antigo (Visual C++)
Exemplo de programa multithread em C
Armazenamento local de threads (TLS)
Simultaneidade e operações assíncronas com C++/WinRT
Multithreading com C++ e MFC