기술나눔

WIN32 핵심 프로그래밍 - 스레드 작업 (2) 동기 상호 배제

2024-07-12

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

목차

경쟁 조건

중요 섹션

뮤텍스

CriticalSection 및 뮤텍스

신호기

이벤트


경쟁 조건

  • 멀티 스레드 환경에서 여러 스레드가 동시에 동일한 데이터에 액세스하거나 수정하는 경우 최종 결과는 스레드 실행 시간입니다.

  • 동기화 메커니즘이 없으면 경쟁 조건이 발생하여 부정확한 데이터나 프로그램 예외가 발생할 수 있습니다.

  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)는 여러 스레드가 동시에 공유 리소스에 액세스하거나 수정하는 것을 방지하는 데 사용됩니다.

  • 동시에 하나의 스레드만 뮤텍스를 소유할 수 있습니다. 하나의 스레드가 뮤텍스의 소유권을 가지면 뮤텍스를 요청하는 다른 스레드는 뮤텍스 권한이 해제될 때까지 차단됩니다.

  • 뮤텍스 만들기 - 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. }

CriticalSection 및 뮤텍스

  • 중요 섹션

    • 공유 리소스에 대한 스레드 동기화 메커니즘인 중요 섹션은 동일한 프로세스의 스레드 간에 상호 배타적인 액세스를 제공합니다.

    • 각 스레드는 공유 리소스에 접근하기 전에 크리티컬 섹션에 들어갈 수 있어야 하고, 접근이 완료된 후 크리티컬 섹션을 떠나 스레드 동기화를 완료할 수 있어야 합니다.

  • 뮤텍스

    • 스레드 동기화 메커니즘은 여러 스레드가 동시에 공유 리소스에 액세스하는 것을 제한하는 데 사용됩니다.

    • 뮤텍스는 프로세스 또는 스레드를 동기화할 수 있으며 프로세스 간에 동기화될 수 있습니다.
      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보다 커질 때까지 차단됩니다.

    • 해방됨 : 세마포어의 값을 높입니다. 이 세마포어를 기다리고 있는 다른 스레드가 차단된 경우 그 중 하나가 깨어납니다.

  • 세마포어 생성

    • 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 프로그래밍에서 이벤트는 여러 스레드 간에 신호를 보내는 데 사용되는 동기화 메커니즘입니다.이벤트 객체는 다음과 같습니다.수동 재설정또는자동 재설정

    • 수동 재설정 이벤트: 이벤트가 설정(신호)되면 명시적으로 재설정될 때까지 이 상태로 유지됩니다. 이는 이벤트를 기다리고 있는 여러 스레드가 이벤트가 재설정되기 전에 깨어날 수 있음을 의미합니다.

    • 자동 재설정 이벤트: 대기 중인 스레드가 이벤트를 수신(신호)하면 시스템은 자동으로 이벤트 상태를 비신호(비신호)로 재설정합니다. 이는 한 번에 하나의 스레드만 깨울 수 있음을 의미합니다.

  • 이벤트 만들기

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