Partage de technologie

Programmation de base WIN32 - Opération de thread (2) Exclusion mutuelle synchrone

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Table des matières

condition de course

Section critique

Mutex

Section critique et Mutex

Sémaphore

Événement


condition de course

  • Dans un environnement multithread, lorsque plusieurs threads accèdent ou modifient les mêmes données en même temps, le résultat final est le temps d'exécution du thread.

  • S'il n'y a pas de mécanisme de synchronisation, des conditions de concurrence se produiront, ce qui peut conduire à des données inexactes ou à des exceptions de programme.

  1. #include <iostream>
  2. #include <windows.h>
  3. DWORD g_Num = 0;
  4. DWORD WINAPI WorkThread(LPVOID lp)
  5. {
  6. for (size_t i = 0; i < 10000000; i++)
  7. {
  8. //g_Num++;
  9. __asm LOCK INC [g_Num]
  10. }
  11. return 0;
  12. }
  13. int main()
  14. {
  15. HANDLE hThread[2] = { 0 };
  16. hThread[0] = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  17. hThread[1] = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  18. WaitForMultipleObjects(2, hThread, TRUE, -1);
  19. std::cout << g_Num << std::endl;
  20. return 0;
  21. }

Section critique

  1. #include <iostream>
  2. #include <windows.h>
  3. DWORD g_Num = 0;
  4. CRITICAL_SECTION cs = { 0 };
  5. DWORD WINAPI WorkThread(LPVOID lp)
  6. {
  7. for (size_t i = 0; i < 1000000; i++)
  8. {
  9. // 进入临界区
  10. EnterCriticalSection(&cs);
  11. // TODO
  12. g_Num++;
  13. // 退出临界区
  14. LeaveCriticalSection(&cs);
  15. }
  16. return 0;
  17. }
  18. int main()
  19. {
  20. HANDLE hThread[2] = { 0 };
  21. // 初始临界区
  22. InitializeCriticalSection(&cs);
  23. hThread[0] = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  24. hThread[1] = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  25. WaitForMultipleObjects(2, hThread, TRUE, -1);
  26. std::cout << g_Num << std::endl;
  27. // 清理临界区
  28. DeleteCriticalSection(&cs);
  29. return 0;
  30. }

Mutex

  • Mutex (Mutex) est utilisé pour empêcher plusieurs threads d'accéder ou de modifier des ressources partagées en même temps.

  • Un seul thread peut posséder le mutex en même temps. Si un thread prend possession du mutex, les autres threads demandant le mutex seront bloqués jusqu'à ce que l'autorisation du mutex soit libérée.

  • Créer un mutex - CreateMutex

  • Demander un mutex - WaitForSingleObject

  • Libérez le mutex - ReleaseMutex

  1. #include <iostream>
  2. #include <windows.h>
  3. HANDLE hMutex = 0;
  4. DWORD g_Num = 0;
  5. DWORD WINAPI WorkThread(LPVOID lp)
  6. {
  7. for (size_t i = 0; i < 100000; i++)
  8. {
  9. WaitForSingleObject(hMutex, INFINITE);
  10. g_Num++;
  11. ReleaseMutex(hMutex);
  12. }
  13. return 0;
  14. }
  15. int main()
  16. {
  17. hMutex = CreateMutex(NULL, FALSE, NULL);
  18. HANDLE hThread1 = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  19. HANDLE hThread2 = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
  20. WaitForSingleObject(hThread1, INFINITE);
  21. WaitForSingleObject(hThread2, INFINITE);
  22. CloseHandle(hThread1);
  23. CloseHandle(hThread2);
  24. CloseHandle(hMutex);
  25. std::cout << g_Num << std::endl;
  26. return 0;
  27. }

Section critique et Mutex

  • section critique

    • Mécanisme de synchronisation des threads pour les ressources partagées, les sections critiques fournissent un accès mutuellement exclusif entre les threads du même processus.

    • Chaque thread doit pouvoir entrer dans la section critique avant d'accéder aux ressources partagées, et quitter la section critique une fois l'accès terminé pour terminer la synchronisation des threads.

  • mutex

    • Le mécanisme de synchronisation des threads est utilisé pour empêcher plusieurs threads d'accéder aux ressources partagées en même temps.

    • Les mutex peuvent synchroniser des processus ou des threads et peuvent être synchronisés entre les processus.
      1. #include <iostream>
      2. #include <Windows.h>
      3. int main()
      4. {
      5. HANDLE hMutex = CreateMutex(NULL, FALSE, L"0xCC_Mutex");
      6. if (hMutex == NULL) return 0;
      7. if (GetLastError() == ERROR_ALREADY_EXISTS)
      8. {
      9. MessageBox(NULL, L"禁止多开", L"错误", MB_OKCANCEL);
      10. return 0;
      11. }
      12. std::cout << "Game Start..." << std::endl;
      13. system("pause");
      14. CloseHandle(hMutex);
      15. return 0;
      16. }
  • performance

    • Les sections critiques sont plus rapides que les mutex au sein des threads du même processus.

  • Fonction

    • Les mutex peuvent être synchronisés entre les processus, mais les sections critiques ne peuvent être synchronisées qu'entre les threads du même processus.

  • la possession

    • Les mutex ont des exigences de propriété strictes, et seuls les threads disposant d'autorisations mutex peuvent les publier.

  • impasse
    • Lorsqu'un thread meurt de manière inattendue alors qu'il détient un verrou (exception)
    • Si un thread se termine de manière inattendue alors qu'il détient un verrou de section critique, le verrou ne sera pas libéré, ce qui empêchera les autres threads en attente de la section critique de s'exécuter normalement, ce qui entraînera un blocage.
      1. #include <iostream>
      2. #include <Windows.h>
      3. CRITICAL_SECTION CriticalSection = { 0 };
      4. DWORD WINAPI WorkThread(LPVOID lp)
      5. {
      6. EnterCriticalSection(&CriticalSection);
      7. printf("TID -> %d rn", GetCurrentThreadId());
      8. Sleep(5000);
      9. LeaveCriticalSection(&CriticalSection);
      10. return 0;
      11. }
      12. int main()
      13. {
      14. InitializeCriticalSection(&CriticalSection);
      15. HANDLE hThread1 = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
      16. Sleep(1000);
      17. TerminateThread(hThread1, 1);
      18. HANDLE hThread2 = CreateThread(NULL, 0, WorkThread, NULL, 0, NULL);
      19. WaitForSingleObject(hThread2, INFINITE);
      20. DeleteCriticalSection(&CriticalSection);
      21. return 0;
      22. }
    • Si un thread se termine de manière inattendue alors qu'il détient un verrou mutex, Windows libère automatiquement sa propriété afin que les autres threads puissent continuer à s'exécuter normalement.
      1. #include <iostream>
      2. #include <Windows.h>
      3. HANDLE hMutex = NULL;
      4. DWORD WINAPI WorkThread1(LPVOID lp)
      5. {
      6. WaitForSingleObject(hMutex, INFINITE);
      7. printf("TID -> %d rn", GetCurrentThreadId());
      8. Sleep(5000);
      9. TerminateThread(GetCurrentThread(), -1);
      10. //todo
      11. return 0;
      12. }
      13. DWORD WINAPI WorkThread2(LPVOID lp)
      14. {
      15. printf("Wait For Thread1 Leavern");
      16. WaitForSingleObject(hMutex, INFINITE);
      17. printf("TID -> %d rn", GetCurrentThreadId());
      18. ReleaseMutex(hMutex);
      19. return 0;
      20. }
      21. int main()
      22. {
      23. hMutex = CreateMutex(NULL, FALSE, NULL);
      24. HANDLE hThread1 = CreateThread(NULL, 0, WorkThread1, NULL, 0, NULL);
      25. Sleep(1000);
      26. HANDLE hThread2 = CreateThread(NULL, 0, WorkThread2, NULL, 0, NULL);
      27. WaitForSingleObject(hThread2, INFINITE);
      28. CloseHandle(hMutex);
      29. CloseHandle(hThread1);
      30. CloseHandle(hThread2);
      31. return 0;
      32. }

Sémaphore

  • Un sémaphore est un objet de synchronisation utilisé pour contrôler l'accès aux ressources partagées par plusieurs threads. C'est un compteur qui représente la quantité de ressources disponibles. Lorsque la valeur du sémaphore est supérieure à 0, cela indique que des ressources sont disponibles ; lorsque la valeur est 0, cela indique qu'aucune ressource n'est disponible.

    • attendez : Tente de diminuer la valeur du sémaphore. Si la valeur du sémaphore est supérieure à 0, décrémentez-la de 1 et poursuivez l'exécution. Si la valeur du sémaphore est 0, le thread se bloque jusqu'à ce que la valeur du sémaphore devienne supérieure à 0.

    • libéré : Augmente la valeur du sémaphore. S'il y a d'autres threads bloqués en attente de ce sémaphore, l'un d'eux sera réveillé.

  • Créer un sémaphore

    • Sur les systèmes Windows, utilisez CreateSemaphore ouCreateSemaphoreEx La fonction crée un sémaphore.

  • Attendez (Attendez) et relâchez (Release) le sémaphore

    • L'attente d'un sémaphore se fait généralement en utilisant WaitForSingleObject ouWaitForMultipleObjects fonction.

    • Libérer l'utilisation du sémaphore ReleaseSemaphore fonction.

  1. #include <iostream>
  2. #include <Windows.h>
  3. #define MAX_COUNT_SEMAPHORE 3
  4. HANDLE g_SemapHore = NULL;
  5. HANDLE g_hThreadArr[10] = { 0 };
  6. DWORD WINAPI WorkThread(LPVOID lp)
  7. {
  8. WaitForSingleObject(g_SemapHore, INFINITE);
  9. for (size_t i = 0; i < 10; i++)
  10. {
  11. std::cout << "COUNT -> " << (int)lp << std::endl;
  12. Sleep(500);
  13. }
  14. ReleaseSemaphore(g_SemapHore, 1, NULL);
  15. return 0;
  16. }
  17. int main()
  18. {
  19. g_SemapHore = CreateSemaphore(
  20. NULL, //安全属性
  21. MAX_COUNT_SEMAPHORE, //初始计数
  22. MAX_COUNT_SEMAPHORE, //最大计数
  23. NULL //信号名称
  24. );
  25. if (g_SemapHore == NULL)
  26. {
  27. std::cout << GetLastError() << std::endl;
  28. return 1;
  29. }
  30. for (size_t i = 0; i < 10; i++)
  31. {
  32. g_hThreadArr[i] = CreateThread(
  33. NULL,
  34. 0,
  35. WorkThread,
  36. (LPVOID)i,
  37. 0,
  38. NULL
  39. );
  40. }
  41. WaitForMultipleObjects(10, g_hThreadArr, TRUE, INFINITE);
  42. //closehandle
  43. return 0;
  44. }

Événement

  • Dans la programmation Windows, les événements sont un mécanisme de synchronisation utilisé pour envoyer des signaux entre plusieurs threads.L'objet événement peut êtreréinitialisation manuelleouréinitialisation automatique

    • Événement de réinitialisation manuelle : lorsqu'un événement est défini (signalé), il restera dans cet état jusqu'à ce qu'il soit explicitement réinitialisé. Cela signifie que plusieurs threads en attente de l'événement peuvent être réveillés avant que l'événement ne soit réinitialisé.

    • Événement de réinitialisation automatique : lorsqu'un événement est reçu (signalé) par un thread en attente, le système réinitialise automatiquement l'état de l'événement sur non signalé (non signalé). Cela signifie qu'un seul thread peut être réveillé à la fois.

  • Créer un évènement

    • Utilisation des fonctions de l'API WindowsCreateEventUn objet événement peut être créé

    • lpEventAttributes : Pointeur vers l'attribut de sécurité, s'il est défini surNULL, la sécurité par défaut est utilisée.

    • bManualReset: siTRUE, alors un événement de réinitialisation manuelle est créé, sinon un événement de réinitialisation automatique est créé.

    • bInitialState: siTRUE, alors l'état initial est l'état du signal si ;FALSE, c'est un état sans signalisation.

    • lpName: Le nom de l'évènement.

  • Pour définir un événement (définir l'état de l'événement sur l'état du signal), utilisezSetEventfonction

  • Pour réinitialiser un événement (définir l'état de l'événement sur un état non signalé), utilisezResetEventfonction

  • Attendez que l'événement attende qu'un objet événement devienne un état de signal.WaitForSingleObjectfonction

  1. #include <iostream>
  2. #include <Windows.h>
  3. DWORD WINAPI WorkThread(LPVOID lp)
  4. {
  5. HANDLE hEvent = *(HANDLE*)lp;
  6. std::cout << "Thread - " << GetCurrentThreadId() << " Waiting For Event" << std::endl;
  7. WaitForSingleObject(hEvent, INFINITE);
  8. std::cout << "Thread - " << GetCurrentThreadId() << " actived" << std::endl;
  9. return 0;
  10. }
  11. int main()
  12. {
  13. HANDLE hThreads[3] = { 0 };
  14. HANDLE hEvent = NULL;
  15. hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  16. if (hEvent == NULL) return 0;
  17. for (size_t i = 0; i < 3; i++)
  18. {
  19. hThreads[i] = CreateThread(NULL, 0, WorkThread, &hEvent, 0, NULL);
  20. }
  21. Sleep(2000);
  22. SetEvent(hEvent);
  23. WaitForMultipleObjects(3, hThreads, TRUE, INFINITE);
  24. CloseHandle(hEvent);
  25. return 0;
  26. }