Critical Section
- n개의 쓰레드가 공통적인 변수(정적 혹은 전역)를 조작할 때
- 이 조작하는 공간은 1개의 쓰레드만 지나가야 한다 라고 정해놓은 게 임계영역(Critical Section)이다.
- 여러 흐름이 일렬로 지나가는 것이므로 직렬화 (Serialize) 된다고도 얘기한다.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <process.h>
#include <tchar.h>
using namespace std;
CRITICAL_SECTION cs;
UINT __stdcall foo(void* p)
{
static int n = 0;
EnterCriticalSection(&cs);
//n = 100;
//임계영역 (Critical Section)
LeaveCriticalSection(&cs);
return 0;
}
int main()
{
InitializeCriticalSection(&cs);
HANDLE thread1 = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
HANDLE thread2 = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
HANDLE hArr[2] = { thread1, thread2 };
WaitForMultipleObjects(2, hArr, TRUE, INFINITE);
DeleteCriticalSection(&cs);
CloseHandle(thread1);
CloseHandle(thread2);
return 0;
}
- CRITICAL_SECTION 구조체를 전역에 만듦
- 메인 함수에서 InitializeCriticalSection 함수로 초기화를 해줌
- 직렬화되어야 할 부분의 시작지점에 EnterCriticalSection 함수를 사용
- 끝나고 LeaveCriticalSection을 사용해서 직렬화 영역 해제
- 끝나고 DeleteCriticalSection으로 메모리 해제
- 위 함수들은 모두 매개변수로 CRITICAL_SECTION의 주소를 받는다.
Spin Count
- 쓰레드 2개가 임계 영역을 지날 때에는 1개가 들어가면 다른 1개는 대기 상태에 놓임
- 운영 체제는 active thread list와 blocked thread list의 링크드리스트를 따로 관리하는데
- 이 상황에서는 active thread list에 있는 쓰레드를 blocked thread list로 옮김
CPU가 1개일 때는 쓰레드 2개가 동시에 일을 할 때 퀀텀 타임으로 시간을 나눠서 일을 함
그래서 위에서 설명한 active thread list에서 blocked thread list로 쓰레드를 옮길 때 일이 끝날 가능성이 없음
(왜냐하면, 동시에 실행되지 않으므로)
- 그러나 CPU가 2개 이상일 때는 OS가 쓰레드를 옮길 때 작업이 끝날 수 있으므로,
- EnterCriticalSection을 한 번 시도하는 것이 아닌 1000~2000번 시도하는 것이 성능 향상에 도움이 된다.
- 멀티코어의 경우 초기화할 때 InitializeCriticalSectionAndSpinCount 함수를 적고, 두 번째 인자로 횟수를 넘겨주면 된다.
- 그러면 멀티코어에서는 확실히 성능 향상을 볼 수 있음
- 싱글코어에서는 두 번째 인자는 무시됨
Mutex
- 공유 자원을 하나의 스레드가 독점적으로 사용할 수 있게 해주는 동기화 객체
뮤택스의 실생활 예시)
화장실 1칸이 있는데 당연하게도 한 명씩밖에 사용할 수 없음
열쇠를 가지고 화장실에 들어가서 볼일을 본 후 나와서 다시 걸어두면
다음 사람이 열쇠를 가지고 들어감
여기서 열쇠는 자원을 독점할 수 있는 열쇠
- Mutex 커널 오브젝트를 만든다.
- 이 함수의 반환값으로 핸들을 반환하며 KMUTANT라는 이름으로 운영체제가 관리
- 뮤택스의 특징으로는 소유자와 소유 횟수라는 값을 가진다.
- lpMutexAttributes - 보안 속성, 0
- lpName - 정할 이름
- dwFlags - 이 프로세스가 소유할건지, 안할건지
- dwDesiredAccess - 권한, MUTEX_ALL_ACCESS
- 소유자가 있으면 논 시그널, 소유자가 없으면 시그널이 된다.
- WaitForSingleObject로 Mutex를 통과 시 쓰레드가 Mutex를 소유하게 된다.
- 그러면 시그널 필드가 논 시그널이 되고, 소유자는 내가 되며, 소유 횟수가 1 증가한다.
- 프로그램을 한 번 더 실행하면 동일한 이름으로 또 뮤택스를 만들텐데
- 윈도우에서는 동일한 이름의 커널 오브젝트를 만들 수 없다.
- 그래서 그런 경우 기존 뮤택스를 오픈해주고 참조계수가 1이 증가한다. (MKO)
- 소유권을 포기하려면 ReleaseMutex를 해주어야 한다.
여러 쓰레드가 대기중인 경우 순서대로 소유권을 가질까?
먼저 내기했던 게 먼저 소유권을 잡는다는 보장이 없다.
대기중인 것 중 하나가 깨어난다 라고 봐야 함
- 뮤택스를 소유하고 있는 쓰레드가 WaitForSingleObject를 또 하면 그냥 통과한다.
- 대신 Release도 2번 해줘야 함
- 뮤택스를 가지고 있는 쓰레드가 소유권을 반환하지 않고 종료된 경우,
- ABANONED(버려진, 포기된) 뮤택스가 된다.
- 이 땐 뮤택스의 소유권이 자동으로 포기되는데, 공유 자원에 문제가 있을 수 있음
Semaphore
- 위의 뮤택스와 비슷한데 화장실이 n개 있는 형태
- 자원의 개수를 관리하는 동기화 객체
- 자원을 한정적인 수량만큼 공유함
- CreateSemaphoreEx 함수로 만든다.
- 반환값은 커널 오브젝트 KSEMAPHORE의 핸들을 반환한다.
- 세마포어만의 특징은 현재 카운트와 최대 카운트가 있다.
- lpSemaphoreAttributes - 보안 속성, 0
- lInitialCount - 현재 카운트값
- lMaximumCount - 최대 카운트값
- lpName - 만들 이름
- dwFlags - 사용되지 않음, 0
- dwDesireAccess - 권한, SEMAPHORE_ALL_ACCESS
- WaitForSingleObject를 통과하면 소유권을 갖는다.
- 그리고 세마포어 객체의 카운트가 -1이 되고, 그 값이 0이면 논 시그널 상태가 된다.
- ReleaseSemaphore 함수를 통해 소유권을 포기하고, 빈 자리가 생기면 다른 쓰레드가 들어온다.
Event
- 상태를 이용해서 n개의 쓰레드가 서로간 통신할 수 있게 해주는 동기화 객체
- 두 개의 쓰레드가 있는데,
- 하나는 그림을 다운로드하는 쓰레드고,
- 나머지 하나는 그 그림을 출력하는 쓰레드라고 한다면
- 그림을 출력하는 쓰레드는 그림이 다운로드되었는지 알아야만 한다.
- 이걸 신호를 기준으로 통신할 수 있는데 이 방식을 이벤트라고 한다.
- 하나의 쓰레드가 작업이 완료되었음을 다른 쓰레드에게 알리기 위해 사용한다.
- lpEventAttrivutes - 보안 속성, 0
- lpName - 정할 이름
- dwFlag - 초기 시그널 상태와 reset의 종류 (0이면 논 시그널 상태에 AUTO RESET을 사용한다는 의미)
- dwDesiredAccess - 권한, EVENT_ALL_ACCESS
- 반환값으로 이벤트 커널 오브젝트가 반환됨
- 이벤트의 고유한 특징으로는 3번째 인자 Reset의 종류가 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <process.h>
#include <tchar.h>
using namespace std;
HANDLE hEvent = 0;
UINT __stdcall foo(void* p)
{
WaitForSingleObject(hEvent, INFINITE); // 이벤트가 시그널될 때까지 대기
//일을 하려면 Event객체가 signal 되야 함
// 외부에서 signal상태로 바꿔줘야 함
printf("foo start work\n");
return 0;
}
int main()
{
hEvent = CreateEventEx(0, _T("MyEvent"), 0, EVENT_ALL_ACCESS);
HANDLE hThread = (HANDLE)_beginthreadex(
0, 0, foo, 0, 0, 0);
getchar();
SetEvent(hEvent); // 이벤트를 signal 상태로 만들기
getchar();
CloseHandle(hEvent);
return 0;
}
- AUTO RESET은 WaitForSingleObject 통과 시 자동으로 시그널 필드가 리셋된다.
- MANUAL RESET의 경우 WaitForSingleObject 통과해도 시그널 필드 변화 없음
- 이 때는 ResetEvent 함수를 호출해야 논 시그널 상태로 만들 수 있다.
'운영체제 > [ecourse] Windows Programming' 카테고리의 다른 글
6-2-2. Event 실습 (0) | 2022.09.20 |
---|---|
6-2-1. Semaphore 실습 (0) | 2022.09.20 |
6-1. Thread Basic (0) | 2022.09.20 |
5-4. Stack Memory (0) | 2022.09.19 |
5-3. Heap Memory (0) | 2022.09.18 |
댓글