세마포어와 뮤텍스 비교
- 세마포어 동기화 기법 중 binary 세마포어를 뮤텍스라 함
- 세마포어의 일부로 뮤텍스가 존재
- 윈도우에서는 각각을 기법으로 가져와서 사용
- 임계 영역에 접근하는 키가 여러개면 세마포어 하나면 뮤텍스
- 세마포어는 임계 영역에 들어오는 쓰레드의 수를 제어할 수 있음(키의 갯수에 따라 달라짐)
- 뮤텍스는 키가 하나이기 때문에 임계 영역에 하나의 쓰레드만 들어올 수 있음
뮤텍스의 생성 및 실행
- CreateMutex : 열쇠를 생성하는 함수
-> 커널 오브젝트 생성을 동반하기 때문에 핸들도 반환되고 핸들 테이블의 상속을 결정하는 보안 설정을 함
-> 소유자를 지정하면 열쇠를 만든사람이 소유하는지 열쇠를 만든 사람도 소유의 권한을 가지지 않고 누구나 소유할 수 있는 지를 결정
HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 보안 설정
BOOL bInitialOwner, // 열쇠의 소유
LPCSTR lpName // 뮤텍스 이름
);
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
); // 지정한 오브젝트가 Signaled 상태가 되기를 기다리는 함수
BOOL ReleaseMutex(
HANDLE hMutex
);
- WaitForSingleObject(), ReleasMutex()를 통해서 뮤텍스(열쇠)를 획득하고 반납
- 뮤텍스를 커널 오브젝트라고 생각하면 WaitForSingleObject()가 빠져나오기 위해선 뮤텍스가 Signaled 상태가 되야 함
-> ReleaseMutex()는 뮤텍스를 Signaled 상태가 되게 하는 함수
-> WaitForSingleObject()는 뮤텍스를 Non-Signaled 상태로 바꾸게 되는 함수
- 쓰레드가 임계 영역으로 들어가기 전 뮤텍스를 얻기 위해 뮤텍스 핸들을 인자로 전달하면서 WaitForSingleObject() 함수를 호출하고 뮤텍스가 획득 가능 상태라면 Signaled 상태에 있고 뮤텍스를 획득하면서 임계 영역에 진입
- WaitForSingleObject 함수는 매개변수로 호출한 오브젝트가 Signaled 상태가 되기를 기다리기 때문에 다른 쓰레드는 임계 영역으로의 진입이 불가능
- 임계 영역에 진입한 쓰레드가 빠져나오면서 ReleaseMutex 함수를 호출하면 뮤텍스는 Signaled 상태가 되어 다른 쓰레드가 획득할 수 있게 되어 다른 쓰레드의 진입을 허용함
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <process.h>
#define NUM_OF_GATE 6
LONG gTotalCount = 0; // 둘 이상의 쓰레드가 접근하는 변수
// CRITICAL_SECTION cSection;
HANDLE hMutex;
void IncreaseCount()
{
// EnterCriticalSection(&cSection); // 임계 영역 시작
WaitForSingleObject(hMutex, INFINITE);
gTotalCount++;
// LeaveCriticalSection(&cSection); // 임계 영역 끝
ReleaseMutex(hMutex);
}
unsigned int WINAPI ThreadProc(LPVOID lpParam)
{
for (DWORD i = 0; i < 1000; i++)
{
IncreaseCount();
}
return 0;
}
int _tmain(int argc, TCHAR* argv[])
{
DWORD dwThreadId[NUM_OF_GATE];
HANDLE hThread[NUM_OF_GATE];
// InitializeCriticalSection(&cSection);
hMutex = CreateMutex(NULL, FALSE, NULL);
if (hMutex == NULL)
{
_tprintf(_T("CreateMutex Error : %d\n"), GetLastError());
}
for (DWORD i = 0; i < NUM_OF_GATE; i++)
{
hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, NULL, CREATE_SUSPENDED, (unsigned*)& dwThreadId[i]);
if (hThread[i] == NULL)
{
_tprintf(_T("Thread create error\n"));
return -1;
} // 쓰레드를 생성하지 못하면 NULL이 반환
}
for (DWORD i = 0; i < NUM_OF_GATE; i++)
{
ResumeThread(hThread[i]);
}
WaitForMultipleObjects(NUM_OF_GATE, hThread, TRUE, INFINITE); // 쓰레드가 종료될 때까지 대기
_tprintf(_T("total count : %d \n"), gTotalCount);
for (DWORD i = 0; i < NUM_OF_GATE; i++)
{
CloseHandle(hThread[i]);
}
// DeleteCriticalSection(&cSection);
CloseHandle(hMutex);
return 0;
}
세마포어의 생성 및 실행
HANDLE CreateSemaphoreA(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
// 세마포어가 가지는 열쇠의 개수(값) = 임계 영역에 동시 접근할 수 있는 쓰레드 수
LONG lMaximumCount, // 세마포어가 가질 수 있는 최대 열쇠의 수 (InitialCount 이상)
LPCSTR lpName
);
BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount
);
- WaitForSingleObject, ReleaseSemaphore 함수가 호출되면 세마포어 카운트가 감소 / 증가됨
- 세마포어 카운트 값이 0이 되지 않는 이상 Non-signaled 상태가 되지 않음
- Signaled 상태로 계속 있기 때문에 WaitForSingleObject를 호출해도 임계 영역에 다른 쓰레드도 진입 가능
#include <stdio.h>
#include <tchar.h>
#include <time.h>
#include <windows.h>
#include <process.h>
#define NUM_OF_CUSTOMER 20
#define RANGE_MIN 10
#define RANGE_MAX (30-RANGE_MIN)
#define TABLE_CNT 10
HANDLE hSemaphore;
DWORD randTimeArr[50];
void TakeMeal(DWORD time)
{
WaitForSingleObject(hSemaphore, INFINITE);
_tprintf(_T("Enter Customer %d ~ \n"), GetCurrentThreadId());
_tprintf(_T("Customer %d having launch ~ \n"), GetCurrentThreadId());
Sleep(1000 * time);
ReleaseSemaphore(hSemaphore, 1, NULL);
_tprintf(_T("Out Customer %d ~ \n\n"), GetCurrentThreadId());
}
unsigned int WINAPI ThreadProc(LPVOID lpParam)
{
TakeMeal((DWORD)lpParam);
return 0;
}
int _tmain(int argc, TCHAR* argv[])
{
DWORD dwThreadIDs[NUM_OF_CUSTOMER];
HANDLE hThreads[NUM_OF_CUSTOMER];
srand((unsigned)time(NULL));
for (int i = 0; i < NUM_OF_CUSTOMER; i++)
{
randTimeArr[i] = DWORD((double)rand() / (double)RAND_MAX * RANGE_MAX + RANGE_MIN);
}
hSemaphore = CreateSemaphore(NULL, TABLE_CNT, TABLE_CNT, NULL);
if (hSemaphore == NULL)
{
_tprintf(_T("Create Semaphore error : %d\n"), GetLastError());
}
for (int i = 0; i < NUM_OF_CUSTOMER; i++)
{
hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, (void*)randTimeArr[i], CREATE_SUSPENDED, (unsigned*)& dwThreadIDs[i]);
if (hThreads[i] == NULL)
{
_tprintf(_T("Create Thread error : %d\n"), GetLastError());
}
}
for (int i = 0; i < NUM_OF_CUSTOMER; i++)
{
ResumeThread(hThreads[i]);
}
WaitForMultipleObjects(NUM_OF_CUSTOMER, hThreads, TRUE, INFINITE);
_tprintf(_T("----------END----------"));
for (int i = 0; i < NUM_OF_CUSTOMER; i++)
{
CloseHandle(hThreads[i]);
}
CloseHandle(hSemaphore);
return 0;
}
- 생성하는 쓰레드의 개수 : NUM_OF_CUSTOMER
- 임계영역에 한번에 접근할 수 있는 쓰레드의 수 = TABLE_CNT
- 처음에 실행하면 20개중 10개의 쓰레드가 임계 영역에 진입하고 세마포어 카운트가 0이 됨
- 주어진 Sleep 시간 이후 임계 영역을 나오면서 ReleaseSemaphore 함수를 통해 세마포어 카운드를 증가시킴
- 쓰레드가 빠져 나오면 기다리고 있던 쓰레드가 다시 들어가고 세마포어 카운트가 증가
- 이후 모든 쓰레드가 진입 후 나오게 되면서 세마포어 카운트가 다시 TABLE_CNT가 됨
'Programming > System Programming' 카테고리의 다른 글
윈도우즈 시스템 프로그래밍 - 14. 쓰레드 동기화 기법2(1) (0) | 2020.08.09 |
---|---|
윈도우즈 시스템 프로그래밍 - 13. 쓰레드 동기화 기법1(4) (0) | 2020.08.05 |
윈도우즈 시스템 프로그래밍 - 13. 쓰레드 동기화 기법1(2) (0) | 2020.08.03 |
윈도우즈 시스템 프로그래밍 - 13. 쓰레드 동기화 기법1(1) (0) | 2020.08.03 |
윈도우즈 시스템 프로그래밍 - 12. 쓰레드의 생성과 소멸(3) (0) | 2020.07.26 |