技術共有

WIN32 コア プログラミング - スレッド操作 (2) 同期相互排他

2024-07-12

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

目次

競合状態

クリティカルセクション

ミューテックス

クリティカルセクションとミューテックス

セマフォ

イベント


競合状態

  • マルチスレッド環境では、複数のスレッドが同時に同じデータにアクセスまたは変更する場合、最終的な結果はスレッドの実行時間になります。

  • 同期メカニズムがない場合、競合状態が発生し、不正確なデータやプログラムの例外が発生する可能性があります。

  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. }

クリティカルセクション

  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 (ミューテックス) は、複数のスレッドが同時に共有リソースにアクセスしたり変更したりするのを防ぐために使用されます。

  • ミューテックスを同時に所有できるのは 1 つのスレッドだけです。1 つのスレッドがミューテックスの所有権を取得した場合、ミューテックスの権限が解放されるまで、ミューテックスを要求している他のスレッドはブロックされます。

  • ミューテックスを作成する - CreateMutex

  • ミューテックスのリクエスト - WaitForSingleObject

  • ミューテックスを解放する - 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. }

クリティカルセクションとミューテックス

  • クリティカルセクション

    • 共有リソースのスレッド同期メカニズム、クリティカル セクションは、同じプロセスのスレッド間で相互排他的アクセスを提供します。

    • 各スレッドは、共有リソースにアクセスする前にクリティカル セクションに入り、アクセスが完了してスレッドの同期を完了した後にクリティカル セクションから出ることができる必要があります。

  • ミューテックス

    • スレッド同期メカニズムは、複数のスレッドが共有リソースに同時にアクセスすることを制限するために使用されます。

    • ミューテックスはプロセスまたはスレッドを同期したり、プロセス間で同期したりできます。
      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. }
  • パフォーマンス

    • クリティカル セクションは、同じプロセスのスレッド内のミューテックスより高速です。

  • 関数

    • ミューテックスはプロセス間で同期できますが、クリティカル セクションは同じプロセス内のスレッド間でのみ同期できます。

  • 所有

    • ミューテックスには厳密な所有権要件があり、ミューテックス権限を持つスレッドのみがミューテックスを解放できます。

  • デッドロック
    • ロックを保持しているときにスレッドが予期せず終了した場合 (例外)
    • クリティカル セクションのロックを保持したままスレッドが予期せず終了すると、ロックは解放されず、クリティカル セクションを待機している他のスレッドが正常に実行できなくなり、デッドロックが発生します。
      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. }
    • ミューテックス ロックを保持しているときにスレッドが予期せず終了した場合、Windows はその所有権を自動的に解放し、他のスレッドが正常に実行を継続できるようにします。
      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. }

セマフォ

  • セマフォは、複数のスレッドによる共有リソースへのアクセスを制御するために使用される同期オブジェクトです。利用可能なリソースの量を表すカウンターです。セマフォの値が 0 より大きい場合は、リソースが使用可能であることを示し、値が 0 の場合は、使用可能なリソースがないことを示します。

    • 待って : セマフォの値を減らそうとします。セマフォ値が 0 より大きい場合は、値を 1 減らして実行を続行します。セマフォの値が 0 の場合、スレッドはセマフォの値が 0 より大きくなるまでブロックします。

    • 解放された : セマフォの値を増やします。このセマフォを待機している他のスレッドがブロックされている場合、そのうちの 1 つが起動されます。

  • セマフォの作成

    • Windows システムでは、次を使用します。 CreateSemaphore またはCreateSemaphoreEx 関数はセマフォを作成します。

  • 待機 (Wait) および解放 (Release) セマフォ

    • セマフォの待機は通常、次を使用して行われます。 WaitForSingleObject またはWaitForMultipleObjects 関数。

    • セマフォの使用を解放する ReleaseSemaphore 関数。

  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. }

イベント

  • Windows プログラミングでは、イベントは複数のスレッド間で信号を送信するために使用される同期メカニズムです。イベント オブジェクトは次のとおりです。手動リセットまたは自動リセット

    • 手動リセット イベント: イベントが設定される (通知される) と、明示的にリセットされるまでこの状態が維持されます。これは、イベントがリセットされる前に、イベントを待機している複数のスレッドが起動される可能性があることを意味します。

    • 自動リセット イベント: 待機中のスレッドによってイベントが受信される (シグナルされる) と、システムはイベントの状態を自動的に非シグナル (非シグナル) にリセットします。これは、一度に 1 つのスレッドのみを起動できることを意味します。

  • イベントの作成

    • Windows API関数の使用CreateEventイベントオブジェクトを作成できる

    • lpEventAttributes: セキュリティ属性へのポインタ (に設定されている場合)NULLの場合、デフォルトのセキュリティが使用されます。

    • bManualReset: もしTRUEの場合は手動リセット イベントが作成され、それ以外の場合は自動リセット イベントが作成されます。

    • bInitialState: もしTRUEの場合、初期状態は信号状態です。FALSE、それは非信号状態です。

    • lpName: イベントの名前。

  • イベントを設定するには (イベント状態を信号状態に設定するには)、次を使用します。SetEvent関数

  • イベントをリセットするには (イベント状態を非シグナル状態に設定するには)、次を使用します。ResetEvent関数

  • イベントオブジェクトがシグナル状態になるまで待機します。WaitForSingleObject関数

  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. }