티스토리 뷰

190703-2
 

교육을 받으면서 노트필기 했던 내용을 날것 그대로 업로드합니다.


 
  • 동기화 : 
  • 비동기화 : 시간 t에 독립적으로 두개 이상 작업이 수행 (중첩, 겹침)
 
크리티컬 색션 : 동기화 객체라고 부른다.
 
  • 동기화 객체
    • InterLocked~
    • Critical Section : User Level
    • Mutex : Kernel Level
    • Semaphore : Kernel Level : ↑ 데이터 보호를 위해 만들어졌다.
    • Event : Kernel Level : 순서제어를 위해 만들어 졌다.

  • 유저모드 동기화
    • 크리티컬 섹션 : 메모리 접근 동기화
    •   인터락 함수 : 메모리 접근 동기화
  • 커널모드 동기화
    •        뮤텍스 : 메모리 접근 동기화 (소유개념), 소유하면 비신호, 소유하고있지않으면 신호 상태가된다.
    •      세마포어 : 메모리 접근 동기화 (소유개념이 없다.) 소유자가 아니더라도 Release를 할 수 있다. 자원안에 자원을 사용할 수 있는 개수를 정할 수 있다. 세마포어가 한개이더라도 한개가 자원을 동시에 갖도록할 수 있다. (자원의 개수가 남아있으면 신호상태), 카운팅 기능이 존재한다.
    •  named 뮤텍스 : 프로세스간 동기화 - 이름을 가진다면 여러 프로세스가 공유 가능하다.
    •        이벤트 : 시행순서 동기화
  • 뮤텍스는 획득한 스레드가 직접 반환해주어야한다.
  • 세마포어나 그 외 동기화 오브젝트는 다른 곳에서 대신 반환해줘도 된다.
데이터보호순서제어에 사용된다.
 
● 임계영역(Critical Section) 설정
    for(int i = 0 ; i < 1000000 ; ++i)
    {
        EnterCriticalSection(&cs); //임계 영역(보호) 시작
        val++;
        LeaveCriticalSection(&cs); //임계 영역 끝
    }
 
#include <stdio.h>
#include <windows.h>
 
CRITICAL_SECTION cs; //● os가 임계 영역을 관리하는데 사용된다.
 
int val=0;
DWORD __stdcall ThreadFunc(LPVOID pParam)
{
    printf("ThreadFunc Param : %d 생성\n", GetCurrentThreadId());
    for(int i = 0 ; i < 1000000 ; ++i)
    {
        EnterCriticalSection(&cs); //임계 영역(보호) 시작
        val++;
        LeaveCriticalSection(&cs); //임계 영역 끝
    }
 
    return 0;
}
 
void main()
{
    DWORD threadID;
    HANDLE hThread[2];
    InitializeCriticalSection(&cs); // 생성 //● 초기화가 필요하다.
 
    hThread[0] = CreateThread(0,0,ThreadFunc, (LPVOID)0, 0, &threadID);
    hThread[1] = CreateThread(0,0,ThreadFunc, (LPVOID)0, 0, &threadID);
 
 
    //쓰레드 종료 대기(모든 쓰레드의 신호상태를 기다림)
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    printf("val : %d\n", val);
 
    DeleteCriticalSection(&cs); //제거 //● 사용 다 했을 경우 제거한다.
 
    CloseHandle(hThread);
}
 
Sleep(0) : 블로킹 상태에 놓겠다는 뜻. 시간 대기는 안함
 
● 뮤텍스 : 소유라는 개념이 있다.
 
소유가 되면 비신호
소유 되있지 않으면 신호상태
#include <stdio.h>
#include <windows.h>
 
HANDLE hMutex; // ●커널오브젝트라 핸들을 가짐.
 
DWORD __stdcall ThreadFunc(LPVOID pParam)
{
    int num = (int)pParam;
    printf("ThreadFunc Param : %d 생성\n", num);
 
    WaitForSingleObject(hMutex, INFINITE);// ● 신호상태를 대기 - 자기 자신(쓰레드)을 블로킹 상태로 만든다.
    // ●호출시 신호상태였다면 해당 쓰레드가 소유하게된다.(비신호상태로 만든다)
    //핸들로 뮤텍스를 준다면 기다리다가 소유하게된다. (ES사 실기시험)
    for(int i = 0 ; i < 10 ; ++i)
    {
        printf("ThreadFunc[%d] : %d\n",num, i);
        Sleep(200);
    }
    ReleaseMutex(hMutex);// ● 소유권을 해제(신호상태로 만든다.)
 
    return 0;
}
 
void main()
{
    DWORD threadID;
    HANDLE hThread[2];
 
    hMutex = CreateMutex( // ● 뮤텍스 생성 -> 핸들 저장
                NULL, //보안 속성 // ●상속여부
                FALSE, //초기 소유 // ●호출하는 쓰레드가 소유할지
                "myMutex" // 이름 // ●아이디처럼 사용되는 문자열 이름. 유니크해야한다. (NULL 사용시 현 프로세스 내에서만 사용가능)
                );
 
    hThread[0] = CreateThread(0,0,ThreadFunc, (LPVOID)1, 0, &threadID);
    hThread[1] = CreateThread(0,0,ThreadFunc, (LPVOID)2, 0, &threadID);
 
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
 
    CloseHandle(hMutex); //제거
 
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
}
 
뮤텍스
#include <stdio.h>
#include <windows.h>
 
HANDLE hMutex;
 
DWORD __stdcall ThreadFunc(LPVOID pParam)
{
    int num = (int)pParam;
    printf("ThreadFunc Param : %d 생성\n", num);
 
    WaitForSingleObject(hMutex, INFINITE);
    WaitForSingleObject(hMutex, INFINITE);//2번 획득 //● 소유 카운트가 한 번 더 증가된다.
    // 내부적으로 소유 카운트를 갖는다. 얻은만큼 받납을 해야한다.
 
    for(int i = 0 ; i < 10 ; ++i)
    {
        printf("ThreadFunc[%d] : %d\n",num, i);
        Sleep(200);
    }
    ReleaseMutex(hMutex); //1번 반환 //● 한 번만 릴리즈.
    //ReleaseMutex(hMutex);
 
    return 0;
}
 
void main()
{
    DWORD threadID;
    HANDLE hThread;
 
    hMutex = CreateMutex(
                NULL, //보안 속성
                FALSE, //초기 소유
                "myMutex" // 이름
                );
 
    hThread = CreateThread(0,0,ThreadFunc, (LPVOID)1, 0, &threadID);
    Sleep(100); // 서브스레드가 뮤텍스를 먼저 소유하도록 Sleep() 사용
 
    DWORD idx = WaitForSingleObject(hMutex, INFINITE); //● 반환값에 따라서 뮤텍스의 상태값을 볼 수 있다.
    switch( idx )
    {
    case WAIT_OBJECT_0:
        printf("정상적으로 뮤텍스 획득\n");
        break;
    case WAIT_ABANDONED:
        printf("뮤텍스를 소유한 쓰레드가 Release하지 않고 종료함\n"); // 획득 수만큼 릴리즈 하지 않은 경우!
        break;
    case WAIT_TIMEOUT:
        printf("설정 시간을 초과함\n"); // 타임 (INFINITE)를 1000 등으로 줄였을 때 발생
        break;
    case WAIT_FAILED:
        printf("핸들이 유효하지 않음!\n"); // 핸들을 먼저 close 했을 경우
    }
        
 
    CloseHandle(hMutex);
 
    CloseHandle(hThread);
}
 
 
이름있는 뮤텍스
#include <windows.h>
#include <TCHAR.H>
#include <vector>
using namespace std;
 
HANDLE hMutex;
 
struct TP { // 쓰레드 파라미터
    HWND hwnd;
    POINT pt; // 클릭한 좌표
};
 
DWORD __stdcall DrawThread(LPVOID pParam);
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam);
 
HINSTANCE g_hInst;
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    생략
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam)
{
    static vector<HANDLE> threadList;
    TP* p;
    HANDLE hThread;
    static DWORD threadID;
    switch (iMsg)
    {
    case WM_CREATE:
        //hMutex = CreateMutex(NULL, FALSE, NULL);
        hMutex = CreateMutex(NULL,FALSE,_T("Unique_Mutex")); // 이름있는 뮤텍스 -> 다른 프로세스에서도 적용된다.
        break;
    case WM_LBUTTONDOWN:
    {
        p = new TP;
        TP& tp = *p;
        tp.hwnd = hwnd;
        tp.pt.x = LOWORD(lParam);
        tp.pt.y = HIWORD(lParam);
 
        hThread = CreateThread(NULL, 0, DrawThread, &tp, 0, &threadID);
        threadList.push_back(hThread);
    }
    break;
    case WM_RBUTTONDOWN:
    {
        static bool brun = true;
        if (brun) {
            for (unsigned i = 0; i < threadList.size(); i++)
            {
                SuspendThread(threadList[i]);
            }
            brun = false;
        }
        else {
            for (unsigned i = 0; i < threadList.size(); i++)
            {
                ResumeThread(threadList[i]);
            }
            brun = true;
        }
    }
    break;
    case WM_DESTROY:
        CloseHandle(hMutex);
        PostQuitMessage(0);
        for (unsigned i = 0; i < threadList.size(); i++)
            CloseHandle(threadList[i]);
        break;
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
DWORD __stdcall DrawThread(LPVOID pParam) { // Worker 쓰레드 라고한다.
    Sleep(1000); // 공유 변수를 쓰게 되면 값을 늦게 가져와서 마우스 클릭이 더 빨랐을 경우 문제가 발생한다 (같은 좌표에 그림)
    TP* p = (TP*)pParam;
    HWND hwnd = p->hwnd;
    HDC hdc = GetDC(hwnd);
    POINT pt = p->pt;
    while (1) {
        WaitForSingleObject(hMutex, INFINITE);
        for (int i = 0; i < 100; i += 5)
        {
            Rectangle(hdc, pt.x, pt.y, pt.x + i, pt.y + i);
            Sleep(40);
        }
        ReleaseMutex(hMutex);
    }
    delete p;
    ReleaseDC(hwnd, hdc);
    return 0;
}
 
세마포어
#include <stdio.h>
#include <windows.h>
 
HANDLE hSema; // 세마포어
 
DWORD __stdcall ThreadFunc(LPVOID pParam)
{
    int num = (int)pParam;
    printf("ThreadFunc Param : %d 생성\n", num);
 
    WaitForSingleObject(hSema, INFINITE);
 
    for(int i = 0 ; i < 10 ; ++i)
    {
        printf("ThreadFunc[%d] : %d\n",num, i);
        Sleep(200);
    }
    LONG previousCount;
    ReleaseSemaphore(hSema, 1, &previousCount);//● 한개를 반환
 
    return 0;
}
 
void main()
{
    DWORD threadID;
    HANDLE hThread;
 
    hSema = CreateSemaphore(
                NULL, //보안 속성
                2, //초기 카운트 ● 2개의 쓰레드가 접근하능하게함 (열쇠의 수)
                2, //최대 카운트 ● 세마포어가 지닐 수 있는 값의 최대 크기
                "mySemaphore" // 이름
                );
 
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)1, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)2, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)3, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)4, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)5, 0, &threadID));
 
    getchar();
 
    CloseHandle(hSema);
}
 
#include <windows.h>
#include <TCHAR.H>
#include <vector>
using namespace std;
 
HANDLE hSema; // 세마포어
 
struct TP { // 쓰레드 파라미터
    HWND hwnd;
    POINT pt; // 클릭한 좌표
};
 
DWORD __stdcall DrawThread(LPVOID pParam);
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam);
 
HINSTANCE g_hInst;
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    기본 코드 생략
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam)
{
    static vector<HANDLE> threadList;
    TP* p;
    HANDLE hThread;
    static DWORD threadID;
    switch (iMsg)
    {
    case WM_CREATE:
        hSema = CreateSemaphore(NULL, 1, 3, NULL);
        
        break;
    case WM_RBUTTONDOWN:
    {
        static LONG prevCount;
        ReleaseSemaphore(hSema, 1, &prevCount);
    }
    break;
    case WM_LBUTTONDOWN:
    {
        p = new TP;
        TP& tp = *p;
        tp.hwnd = hwnd;
        tp.pt.x = LOWORD(lParam);
        tp.pt.y = HIWORD(lParam);
 
        hThread = CreateThread(NULL, 0, DrawThread, &tp, 0, &threadID);
        threadList.push_back(hThread);
    }
    break;
    case WM_DESTROY:
        CloseHandle(hSema);
        PostQuitMessage(0);
        for (unsigned i = 0; i < threadList.size(); i++)
            CloseHandle(threadList[i]);
        break;
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
DWORD __stdcall DrawThread(LPVOID pParam) { // Worker 쓰레드 라고한다.
    Sleep(1000); // 공유 변수를 쓰게 되면 값을 늦게 가져와서 마우스 클릭이 더 빨랐을 경우 문제가 발생한다 (같은 좌표에 그림)
    TP* p = (TP*)pParam;
    HWND hwnd = p->hwnd;
    HDC hdc = GetDC(hwnd);
    POINT pt = p->pt;
    while (1) {
        WaitForSingleObject(hSema, INFINITE);
        for (int i = 0; i < 100; i += 5)
        {
            Rectangle(hdc, pt.x, pt.y, pt.x + i, pt.y + i);
            Sleep(40);
        }
        ReleaseSemaphore(hSema, 1, NULL);
    }
    delete p;
    ReleaseDC(hwnd, hdc);
    return 0;
}
 
  • 이벤트
    • 자동이벤트:비신호 상태로 자동 변환한다. 대기하고있었던 것이 여러개라도 하나만 대기가 풀린다.
    • 수동이벤트:자동변환이 되지 않아서 비신호 상태를 꼭 ResetEvent() 함수로 비신호 상태로 만든다. 수동은 SetEvent()로 신호상태로 만드는 순간 대기를 하고 있던 것들이 모두 통과한다.
 
#include <stdio.h>
#include <windows.h>
 
HANDLE hEvent;
 
DWORD __stdcall ThreadFunc(LPVOID pParam)
{
    int num = (int)pParam;
    printf("ThreadFunc Param : %d 생성\n", num);
 
    WaitForSingleObject(hEvent, INFINITE);
 
    for(int i = 0 ; i < 10 ; ++i)
    {
        printf("ThreadFunc[%d] : %d\n",num, i);
        Sleep(200);
    }
 
    return 0;
}
 
void main()
{
    DWORD threadID;
    HANDLE hThread;
 
    //자동 이벤트: SetEvent()에 의해 Wait 함수들 중 하나만 리턴하고 다시 비신호 이벤트로.
    hEvent = CreateEvent(
                NULL, //보안 속성
                TRUE, //수동 이벤트? TRUE일 경우 수동, FALSE 일경우 자동
                FALSE, //초기 신호 상태
                "myEvent" // 이름
                );
 
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)1, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)2, 0, &threadID));
    CloseHandle(CreateThread(0,0,ThreadFunc, (LPVOID)3, 0, &threadID));
    
    Sleep(10);
    printf("엔터를 누르면 SetEvent() 호출!\n");
    getchar();
    SetEvent(hEvent);
 
    printf("엔터를 누르면 SetEvent() 호출!\n");
    getchar();
    SetEvent(hEvent);
 
    printf("엔터를 누르면 SetEvent() 호출!\n");
    getchar();
    SetEvent(hEvent);
 
    getchar();
    CloseHandle(hEvent);
}
 
PulseEvent : Set 과 ReSet을 동시에 한다.
 
(A,B)->C->D 4개 쓰레드가 돌고 있을 때. 어느 시점이 되면 결과치를 가져와 써야한다. A,B가 완료되었으면 C가 기다리다가 A 와 B의 신호를 기다린다. 그 다음 나머지 작업 완료후 -> D 에 전달 D는 기다리다가 신호가 오면 진행을 한다. (이러한 위상 작업에 이벤트를 사용한다.)
 
자동,수동 이벤트 어디에?
자동은 wait하는 놈 개수만큼 set을 해주어야한다.
 
3개의 쓰레드가 돌고있을 때 set을 설정하는 시점에
  1. 어느 누구도 wait를 호출x 경우 (Set->ReSet 펄스를 준것과 같다.)
  2. 일부만 wait 호출한 경우 (Set->(나머지 하나가 wait 할 수 있다.)->Reset [pulse는 set->reset을 바로한다.(싱크), 대기하던 쓰레드만 풀리고 도중 들어오는건 안된다.]
  3. 모든 참여자들이 wait를 하고 있을경우 () (Set->Rest 펄스를 준것과 같다.)
굳이 왜 2개를 만들어 놓았는가?
  • 네트워크 플밍에서 내가원할 때 비신호,신호를 해야할 때가 온다. 비동기입출력 -> 컴플리션루틴(이벤트매커니즘)
 
#include <windows.h>
#include <TCHAR.H>
#include <vector>
using namespace std;
 
HANDLE hSema; // 세마포어
 
struct TP { // 쓰레드 파라미터
    HWND hwnd;
    POINT pt; // 클릭한 좌표
};
 
DWORD __stdcall DrawThread(LPVOID pParam);
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam);
 
HINSTANCE g_hInst;
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    g_hInst = hInstance;
 
    기본 코드 생략
}
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,
    WPARAM wParam, LPARAM lParam)
{
    static vector<HANDLE> threadList;
    TP* p;
    HANDLE hThread;
    static DWORD threadID;
    switch (iMsg)
    {
    case WM_CREATE:
        hSema = CreateSemaphore(NULL, 0, 3, NULL);
        
        break;
    case WM_RBUTTONDOWN:
    {
        LONG prevCount;
        ReleaseSemaphore(hSema, 1, &prevCount);
        TCHAR buf[100];
        wsprintf(buf, _T("Prev Count : [%d]"), prevCount);
        SetWindowText(hwnd, buf);
        
 
    }
    break;
    case WM_LBUTTONDOWN:
    {
        p = new TP;
        TP& tp = *p;
        tp.hwnd = hwnd;
        tp.pt.x = LOWORD(lParam);
        tp.pt.y = HIWORD(lParam);
 
        hThread = CreateThread(NULL, 0, DrawThread, &tp, 0, &threadID);
        threadList.push_back(hThread);
    }
    break;
 
    case WM_DESTROY:
        CloseHandle(hSema);
        PostQuitMessage(0);
        for (unsigned i = 0; i < threadList.size(); i++)
            CloseHandle(threadList[i]);
        break;
    }
    return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
DWORD __stdcall DrawThread(LPVOID pParam) { // Worker 쓰레드 라고한다.
    Sleep(1000); // 공유 변수를 쓰게 되면 값을 늦게 가져와서 마우스 클릭이 더 빨랐을 경우 문제가 발생한다 (같은 좌표에 그림)
    TP* p = (TP*)pParam;
    HWND hwnd = p->hwnd;
    HDC hdc = GetDC(hwnd);
    POINT pt = p->pt;
    //while (1)
    {
        WaitForSingleObject(hSema, INFINITE);
        for (int i = 0; i < 100; i += 5)
        {
            Rectangle(hdc, pt.x, pt.y, pt.x + i, pt.y + i);
            Sleep(40);
        }
        //ReleaseSemaphore(hSema, 1, NULL);
    }
    delete p;
    ReleaseDC(hwnd, hdc);
    return 0;
}
 
● 플래그
댓글
댓글쓰기 폼