sessão 5 – Gestão básica de threads em Win32
Bibliografia de apoio:
Capítulos 6 e 8 do Livro Windows System Programming (da bibliografia)
MSDN:
Synchronization Objects https://docs.microsoft.com/pt-pt/windows/win32/sync/synchronization-objects
Wait Functions https://msdn.microsoft.com/en-us/library/windows/desktop/ms687069(v=vs.85).aspx
Time Functions https://docs.microsoft.com/en-us/windows/win32/sysinfo/time-functions
Criar e sincronizar threads:
modelo de exclusão mutua ( com mutexes, um semáforo simplificado )
existe uma zona de memória que é partilhada entre threads mas que deve ser usada apenas quando está livre e trancada quando está ocupada (secção critica)
e serve para o mesmo processo ou entre processos
uma thread suspensa podem voltar com ResumeThread( HANDLE )
#include <windows.h> #include <tchar.h> #include <io.h> #include <fcntl.h> #include <stdio.h> #define TAM 200 #define MAX_THREADS 2 BOOL sairThread = FALSE; typedef struct { DWORD total_soma; int lim_inf, lim_sup; } dados_thread; DWORD WINAPI SomaPares(LPVOID lpParametro) { dados_thread* dados = (dados_thread*) lpParametro; _tprintf(TEXT("Sou a thread %d somar pares de %d a %d\n"), GetCurrentThreadId(), dados->lim_inf, dados->lim_sup); for (int i = dados->lim_inf; i <= dados->lim_sup; i++) { if (i % 2 == 0){ dados->total_soma += i; } if (i % 200 == 0){ Sleep(1000); } } return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThreadArray[MAX_THREADS]; //handle threads dados_thread dado[MAX_THREADS]; DWORD tid[MAX_THREADS], resultado; #ifdef UNICODE _setmode(_fileno(stdin), _O_WTEXT); _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); #endif //pedir aqui ao utilizador os intervalos _tprintf(TEXT("qual o valor MIN do 1º intervalo\n")); wscanf_s(TEXT("%d"), &dado[0].lim_inf); _tprintf(TEXT("qual o valor MAX do 1º intervalo\n")); wscanf_s(TEXT("%d"), &dado[0].lim_sup); _tprintf(TEXT("qual o valor MIN do 2º intervalo\n")); wscanf_s(TEXT("%d"), &dado[1].lim_inf); _tprintf(TEXT("qual o valor MAX do 2º intervalo\n")); wscanf_s(TEXT("%d"), &dado[1].lim_sup); dado[0].total_soma = dado[1].total_soma = 0; hThreadArray[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) SomaPares, &dado[0], 0, &(tid[0])); hThreadArray[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) SomaPares, &dado[1], 0, &(tid[1])); if (hThreadArray[0] == NULL || hThreadArray[1] == NULL) { _tprintf(TEXT("nada de threads")); return -1; } //esperar pela primeira para terminar resultado = WaitForMultipleObjects(2, hThreadArray, FALSE, INFINITE); if(resultado == WAIT_OBJECT_0) { _tprintf(TEXT("resultado da thread[%d]: %d\n"), tid[0], dado[0].total_soma); _tprintf(TEXT("resultado da thread[%d]: %d\n"), tid[1], dado[1].total_soma); } CloseHandle(hThreadArray[0]); CloseHandle(hThreadArray[1]); return 0; }
como medir o tempo de execução de um programa no windows?
#include <stdio.h> #include <stdlib.h> #include <windows.h> int main(void) { LARGE_INTEGER frequencia, inicio, fim; double intervalo; QueryPerformanceFrequency(&frequencia); // obter ticks por segundo QueryPerformanceCounter(&inicio); //obter ticks iniciais //executar o codigo QueryPerformanceCounter(&fim); // obter ticks finais intervalo = (double)(fim.QuadPart - inicio.QuadPart) / frequencia.QuadPart; //obter tempo em segundos printf("O codigo demorou %.3f segundos a executar.\n", intervalo); return 0; }
#include <Windows.h> #include <tchar.h> #include <math.h> #include <stdio.h> #include <fcntl.h> #include <io.h> #define MAX_THREADS 20 typedef struct { unsigned int limiteBaixo; unsigned int limiteAlto; unsigned int* contadorPartilhado; } TDados; DWORD WINAPI ContaMultiplosTres(LPVOID lmParam) { unsigned int i; TDados* data = (TDados*)lmParam; for (i = data->limiteBaixo; i <= data->limiteAlto; i++) { if (i % 3 == 0) { (*(data->contadorPartilhado))++; } } return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThreads[MAX_THREADS]; TDados tdados[MAX_THREADS]; unsigned int numeroThreads, i, limSuperior = 400000000, contador = 0; LARGE_INTEGER tickSegundo, inicio, fim; double duracao; #ifdef UNICODE _setmode(_fileno(stdin), _O_WTEXT); _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); #endif if (!QueryPerformanceCounter(&tickSegundo)) { _tprintf(TEXT("Erro: obter ticks por segundo.\n")); } _tprintf(TEXT("\nNumero de threads (max %d)->"), MAX_THREADS); _tscanf_s(TEXT("%u"), &numeroThreads); //lançar as threads (suspensas) for (i = 0; i < numeroThreads; i++) { tdados[i].limiteBaixo = 1 + (limSuperior / numeroThreads) * i; tdados[i].limiteAlto = (limSuperior / numeroThreads) * (i + 1); tdados[i].contadorPartilhado = &contador; _tprintf(TEXT("thread %u: %u a %u\n"), i, tdados[i].limiteBaixo, tdados[i].limiteAlto); hThreads[i] = CreateThread(NULL, 0, ContaMultiplosTres, &tdados[i], CREATE_SUSPENDED, NULL); } //começar o cronometro, ativar as threadas QueryPerformanceCounter(&inicio); for (i = 0; i < numeroThreads; i++) //dividir os intervalos { ResumeThread(hThreads[i]); //ativar as threads } WaitForMultipleObjects(numeroThreads, hThreads, TRUE, INFINITE); //esperar que as threads terminem //esperar que as threads terminem, ler o cronometro QueryPerformanceCounter(&fim); duracao = (double)(fim.QuadPart - inicio.QuadPart) / tickSegundo.QuadPart; _tprintf(TEXT("Contados %u numeros em %lf segundos.\n"), contador, duracao); for (i = 0; i < numeroThreads; i++) { CloseHandle(hThreads[i]); } //existe um problema de sincronização //falta sincronização das threads //o numero ideal de threads é igual ao numero de cores do processador //4 cores, 4 threads //4 cores, 5 thread, fica mais lento... //o escalonamento de processos.. return 0; }
//usar mutexes para garantir acesso em exlucsão mutua ao contador global //createMutexA, waitForSingleObject, ReleaseMutex, CloseHandle #include <Windows.h> #include <tchar.h> #include <math.h> #include <stdio.h> #include <fcntl.h> #include <io.h> #define MAX_THREADS 20 typedef struct { unsigned int limiteBaixo; unsigned int limiteAlto; unsigned int* contadorPartilhado; //receber o mutex HANDLE mutexPartilhado; } TDados; DWORD WINAPI ContaMultiplosTres(LPVOID lmParam) { unsigned int i; TDados* data = (TDados*)lmParam; for (i = data->limiteBaixo; i <= data->limiteAlto; i++) { if (i % 3 == 0) { //esperar no mutex, incrementar, libertar WaitForSingleObject(data->mutexPartilhado, INFINITE); (*(data->contadorPartilhado))++; ReleaseMutex(data->mutexPartilhado); } } return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThreads[MAX_THREADS]; TDados tdados[MAX_THREADS]; unsigned int numeroThreads, i, limSuperior = 4000000, contador = 0; LARGE_INTEGER tickSegundo, inicio, fim; double duracao; //o mutex HANDLE mutex; #ifdef UNICODE _setmode(_fileno(stdin), _O_WTEXT); _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); #endif if (!QueryPerformanceCounter(&tickSegundo)) { _tprintf(TEXT("Erro: obter ticks por segundo.\n")); } _tprintf(TEXT("\nNumero de threads (max %d)->"), MAX_THREADS); _tscanf_s(TEXT("%u"), &numeroThreads); //criar o mutex mutex = CreateMutex(NULL, FALSE, NULL); //lançar as threads (suspensas) for (i = 0; i < numeroThreads; i++) { tdados[i].limiteBaixo = 1 + (limSuperior / numeroThreads) * i; tdados[i].limiteAlto = (limSuperior / numeroThreads) * (i + 1); tdados[i].contadorPartilhado = &contador; //passar o mutex tdados[i].mutexPartilhado = mutex; _tprintf(TEXT("thread %u: %u a %u\n"), i, tdados[i].limiteBaixo, tdados[i].limiteAlto); hThreads[i] = CreateThread(NULL, 0, ContaMultiplosTres, &tdados[i], CREATE_SUSPENDED, NULL); } //começar o cronometro, ativar as threadas QueryPerformanceCounter(&inicio); for (i = 0; i < numeroThreads; i++) //dividir os intervalos { ResumeThread(hThreads[i]); //ativar as threads } WaitForMultipleObjects(numeroThreads, hThreads, TRUE, INFINITE); //esperar que as threads terminem //esperar que as threads terminem, ler o cronometro QueryPerformanceCounter(&fim); duracao = (double)(fim.QuadPart - inicio.QuadPart) / tickSegundo.QuadPart; _tprintf(TEXT("Contados %u numeros em %lf segundos.\n"), contador, duracao); for (i = 0; i < numeroThreads; i++) { CloseHandle(hThreads[i]); } //fechar o mutex CloseHandle(mutex); //agora os resultados já nao vão variar, vão ser sempre os corretos //o tempo de execução aumenta return 0; }
critical section: recurso partilhado (em vez de mutexes)
mecanismo de controlo de acesso: CriticalSections, espécie de mutex, que tambem controla o acesso à secção critica
é uma espera ativa, que vai verificar um determinado numero de vezes se pode entrar
criticalsection é como um mini-mutex optimizado para threads dentro do mesmo processo
a thread tem um periodo de espera ativa à entrada da criticalsection, em que vai consultado para ver se pode entrar um numero de vezes (no max spin count)
pode aliviar o esforço de sinalização entre threads principalmente para spin count baixos
eficicente quando o recurso fica bloqueado por períodos curtos: assim não é preciso suspender e depois sinalizar threads tantas vezes
#include <Windows.h> #include <tchar.h> #include <math.h> #include <stdio.h> #include <fcntl.h> #include <io.h> #define MAX_THREADS 20 typedef struct { unsigned int limiteBaixo; unsigned int limiteAlto; unsigned int* contadorPartilhado; //referencia da criticalSection CRITICAL_SECTION * cirticalSectionPartilhada; } TDados; DWORD WINAPI ContaMultiplosTres(LPVOID lmParam) { unsigned int i; TDados* data = (TDados*)lmParam; for (i = data->limiteBaixo; i <= data->limiteAlto; i++) { if (i % 3 == 0) { //entrada na critical section EnterCriticalSection(data->cirticalSectionPartilhada); (*(data->contadorPartilhado))++; //saida na critical section LeaveCriticalSection(data->cirticalSectionPartilhada); } } return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThreads[MAX_THREADS]; TDados tdados[MAX_THREADS]; unsigned int numeroThreads, i, limSuperior = 4000000, contador = 0; LARGE_INTEGER tickSegundo, inicio, fim; double duracao; //declaração variavel critical section CRITICAL_SECTION criticalSection; #ifdef UNICODE _setmode(_fileno(stdin), _O_WTEXT); _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); #endif if (!QueryPerformanceCounter(&tickSegundo)) { _tprintf(TEXT("Erro: obter ticks por segundo.\n")); } _tprintf(TEXT("\nNumero de threads (max %d)->"), MAX_THREADS); _tscanf_s(TEXT("%u"), &numeroThreads); //inicializar da criticalsection if(!InitializeCriticalSectionAndSpinCount(&criticalSection, 400)) //spinout 400, normal.. { return 0; } //lançar as threads (suspensas) for (i = 0; i < numeroThreads; i++) { tdados[i].limiteBaixo = 1 + (limSuperior / numeroThreads) * i; tdados[i].limiteAlto = (limSuperior / numeroThreads) * (i + 1); tdados[i].contadorPartilhado = &contador; //passar o ponteiro da critical section tdados[i].cirticalSectionPartilhada = &criticalSection; _tprintf(TEXT("thread %u: %u a %u\n"), i, tdados[i].limiteBaixo, tdados[i].limiteAlto); hThreads[i] = CreateThread(NULL, 0, ContaMultiplosTres, &tdados[i], CREATE_SUSPENDED, NULL); } //começar o cronometro, ativar as threadas QueryPerformanceCounter(&inicio); for (i = 0; i < numeroThreads; i++) //dividir os intervalos { ResumeThread(hThreads[i]); //ativar as threads } WaitForMultipleObjects(numeroThreads, hThreads, TRUE, INFINITE); //esperar que as threads terminem //esperar que as threads terminem, ler o cronometro QueryPerformanceCounter(&fim); duracao = (double)(fim.QuadPart - inicio.QuadPart) / tickSegundo.QuadPart; _tprintf(TEXT("Contados %u numeros em %lf segundos.\n"), contador, duracao); for (i = 0; i < numeroThreads; i++) { CloseHandle(hThreads[i]); } //remover a criticalsection DeleteCriticalSection(&criticalSection); //melhoria de desempenho muito consideravel // return 0; }
eventos: atuam como flags para assinalar à thread se pode continuar ou não
podem ser usados para outros cenários que não threads
a usar: CreateEventA, SetEvent
#include <Windows.h> #include <tchar.h> #include <math.h> #include <stdio.h> #include <fcntl.h> #include <io.h> #define MAX_THREADS 20 typedef struct { unsigned int limiteBaixo; unsigned int limiteAlto; unsigned int* contadorPartilhado; CRITICAL_SECTION* cirticalSectionPartilhada; //referência a evento HANDLE eventoPartilhado; } TDados; DWORD WINAPI ContaMultiplosTres(LPVOID lmParam) { unsigned int i; TDados* data = (TDados*)lmParam; //aguardar evento antes de começar, a thread fica à espera que o evento seja TRUE WaitForSingleObject(data->eventoPartilhado, INFINITE); for (i = data->limiteBaixo; i <= data->limiteAlto; i++) { if (i % 3 == 0) { EnterCriticalSection(data->cirticalSectionPartilhada); (*(data->contadorPartilhado))++; LeaveCriticalSection(data->cirticalSectionPartilhada); } } return 0; } int _tmain(int argc, TCHAR* argv[]) { HANDLE hThreads[MAX_THREADS]; TDados tdados[MAX_THREADS]; unsigned int numeroThreads, i, limSuperior = 4000000, contador = 0; LARGE_INTEGER tickSegundo, inicio, fim; double duracao; CRITICAL_SECTION criticalSection; //evento HANDLE evento; #ifdef UNICODE _setmode(_fileno(stdin), _O_WTEXT); _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); #endif if (!QueryPerformanceCounter(&tickSegundo)) { _tprintf(TEXT("Erro: obter ticks por segundo.\n")); } _tprintf(TEXT("\nNumero de threads (max %d)->"), MAX_THREADS); _tscanf_s(TEXT("%u"), &numeroThreads); if (!InitializeCriticalSectionAndSpinCount(&criticalSection, 400)) //spinout 400, normal.. { return 0; } //criar o evento evento = CreateEvent(NULL, TRUE, FALSE, NULL); for (i = 0; i < numeroThreads; i++) { tdados[i].limiteBaixo = 1 + (limSuperior / numeroThreads) * i; tdados[i].limiteAlto = (limSuperior / numeroThreads) * (i + 1); tdados[i].contadorPartilhado = &contador; tdados[i].cirticalSectionPartilhada = &criticalSection; //passar o evento para a thread tdados[i].eventoPartilhado = evento; _tprintf(TEXT("thread %u: %u a %u\n"), i, tdados[i].limiteBaixo, tdados[i].limiteAlto); hThreads[i] = CreateThread(NULL, 0, ContaMultiplosTres, &tdados[i], CREATE_SUSPENDED, NULL); } QueryPerformanceCounter(&inicio); //gerar evento para threads avançarem SetEvent(evento); //o ResumeThread já não é necessário WaitForMultipleObjects(numeroThreads, hThreads, TRUE, INFINITE); //esperar que as threads terminem QueryPerformanceCounter(&fim); duracao = (double)(fim.QuadPart - inicio.QuadPart) / tickSegundo.QuadPart; _tprintf(TEXT("Contados %u numeros em %lf segundos.\n"), contador, duracao); for (i = 0; i < numeroThreads; i++) { CloseHandle(hThreads[i]); } DeleteCriticalSection(&criticalSection); //termina a o evento CloseHandle(evento); //as threds começam todas ao mesmo tempo, o tempo vai demorar //este mecanismo permite desbloquear um processo return 0; }
0 thoughts on “sessão 5 – Gestão básica de threads em Win32”