반응형

※ 커널 오브젝트가 생성될 때는 non-signaled 상태에 있다가 종료하는 경우에 signaled상태로 바뀐다.

※ nonsignaled상태이면 WaitForSingleObject가 멈추고 그렇지 않으면 통과(리턴)할 수 있다.


19-1. 쓰레드 동기화 기법의 분류

프로세스 실행의 두 가지 모드

사용자 모드(User Mode)와 커널 모드(Kernel Mode)


user mode : 시스템 리소스를 제외하고 원하는대로 사용가능. 

접근할 수 있는 메모리 공간이 제한되어 있음.

물리적 영역으로 직접 접근이 허용되지 않는다.

사용자 모드 동기화 기법 : CRITICAL_SECTION 오브젝트 사용

장점 : 프로그래밍하기 수월, 속도가 빠르다.

단점 : 커널 모드 동기화 기법에 비해 제한된 기능만 가진다.


kernel mode : 시스템 리소스를 접근할 수 있지만 시스템 콜을 통해서만 운영체제에서 직접 처리한다

시스템의 모든 메모리 영역으로 접근 가능.

시스템 리소스 : 파일, 쓰레드

시스템의 안전성을 보장하기 위해서 제공

커널 모드 동기화 기법 : Event, Semaphore, Mutex (다른 프로세스에서 접근 가능)

장점 : Deadlock 문제를 막을 수 있다. 둘 이상의 프로세스 내에 존재하는 쓰레드 간의 동기화 가능. 

단점 : 실행 속도의 저하가 발생.


19-2. CRITICAL_SECTION

동기화 관련 함수

※ lpCriticalSection : CRITICAL_SECTION 변수의 포인터

CS 초기화 : void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 소유(임계영역에 들어갈 때) : void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 반환(임계영역을 나올 때) : void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 소멸 : void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);


커널 오브젝트의 상태

뮤텍스

signaled 상태 -> 뮤텍스 소유가 가능한 상태

non-signaled 상태 -> 뮤텍스가 이미 가지고 있는(소유되어진) 상태

세마포어

signaled 상태 -> 세마포어 카운트가 0이 아닌 경우.

non-signaled 상태 -> 세마포어 카운트가 0인 경우.


BOOL CloseHandle(HANDLE hObject);

커널 오브젝트 객체를 소멸한다. 참조카운트 1 감소.


19-3. Mutex(Mutual Exclusion)

HANDLE CreateMutex(

    LPSECURITY_ATTRIBUTES lpMutexAttributes,

    BOOL bInitialOwner,

    LPCTSTR lpName

);

뮤텍스 객체를 생성하고 참조카운트 1 증가.

리턴 : 성공시 생성된 Mutex 오브젝트 핸들, 실패시

lpMutexAttributes : 보안설정

bInitialOwner : false는 signal, true는 non-signal상태로

lpName : 객체 이름


※ WaitSingleObject함수는 뮤텍스를 가져오는(lock) 함수

BOOL ReleaseMutex(

    HANDLE hMutex

);

Mutex를 돌려주는(unlock)하는 함수.

리턴 : 성공시 TRUE 실패시 FALSE

non-signaled -> signaled


Mutex를 통한 쓰레드 동기화


19-4. Semaphore

세마포어가 0이 되어야 세마포어 오브젝트 non-signaled 상태가 된다.

HANDLE CreateSemaphore(

    LPSECURITY_ATTRIBTE lpSemaphoreAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName

);

세마포어 객체를 생성하고 참조카운트 1 증가.

리턴 : 성공시 생성된 Mutex 오브젝트 핸들, 실패시

lpMutexAttributes : 보안설정

lInitialCount : 초기 세마포어값.

lMaximumCount : 세마포어의 최대값.

lpName : 객체 이름


※ WaitForSingleObject는 세마포어 값을 감소시킨다. signaled상태 -> nonsignaled상태로

BOOL ReleaseSemaphore(

    HANDLE hSemaphore,

    LONG lReleaseCount,

    LPLONG lpPreviousCount

);

리턴 : 성공시 TRUE, 실패시 FALSE

hSemaphore : 세마포어의 핸들.

lReleaseCount : 세마포어 값을 증가시킬 크기

lpPreviousCount : 이 함수를 호출하기 전에 카운트 값을 저장할 수 있다.


19-5. Event

Event 특징 : auto & manual-reset 모드의 커널 오브젝트의 생성이 가능하다.

※ 대기 중에 있는 쓰레드들을 한꺼번에 깨울 수 있다. (장점)

HANDLE CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes,

    BOOL bManualReset,

    BOOL bInitialState,

    LPCTSTR lpName

);

이벤트를 생성한다.

리턴 : 성공시 생성된 Event 오브젝트 핸들. 실패시 NULL

lpEventAttributes : 보안설정

bManualReset : TRUE면 manual-reset 모드의 이벤트 생성. 

bInitialState : TRUE면 signaled 상태의 이벤트 생성, FALSE면 non-signaled 상태의 Evenet가 생성

lpName : Event에 이름을 준다.


manual-reset 모드 Event 동기화 원리

1. hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); -> 쓰레드 생성하면 대기상태로 생성.

2. SetEvent(hEvent);

3. NumberOfA, NumberOfOthers가 수행됨.

반응형
반응형

17-1. 프로세스(Process)와 쓰레드(Thread)

쓰레드란 무엇인가?

경량화된 프로세스 

동시 실행 가능.


프로세스와 차이점

스택, PC를 포함한 레지스터 복사본, 쓰레드 지역 보관영역을 제외한 나머지 메모리 영역을 공유.

※ fork를 통해 프로세스 복사했다면 부모와 자식 프로세스는 서로 독립적으로 작동한다.

data : 전역변수가 저장되는 공간.

heap : 동적할당을 하는 메모리 공간.

stack : 지역변수를 위해 사용하는 공간.

보다 간단한 컨텍스트 스위칭.

일부 메모리를 공유하므로 쓰레드간 통신이 편리.

(프로그래밍 하는데 부담이 될 수 있음.)


17-2. 쓰레드 생성하기

쓰레드란 무엇인가?

프로세스(LINUX) : 실행되고 있는 일의 단위

쓰레드 : 프로세스 내에서 다시 나누어지는 일의 단위. 프로세스가 존재해야 쓰레드 생성이 가능.


#include<pthread.h>

int pthread_create(pthread_t * thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

쓰레드를 생성한다. nonblocking함수.

리턴 : 성공시 0, 실패시 이외 값.

thread : 생성된 쓰레드의 ID를 저장할 변수의 포인터.

attr : 생성하고자 하는 쓰레드의 특성. 일반적으로 NULL을 전달.

start_routine : 리턴타임이 void*이고 인자도 void*인 함수를 가리키는 포인터

arg : 쓰레드에 의해 호출되는 함수에(start_routine 포인터가 가리키는 함수) 전달하고자 하는 인자 값.


※ main쓰레드가 먼저 죽으면 자식 쓰레드는 끝까지 실행되지 않고 종료될 수 있다.


#include<pthread.h>

int pthread_join(pthread_t th, void **thread_return);

리턴 : 성공시 0, 실패시 이외 값.

th : 인자로 들어오는 ID의 쓰레드가 종료할 때까지 실행을 지연시킨다. blocking 함수.

thread_return : 쓰레드가 종료 시 반환하는 값에 접근할 수 있는 2차원 포인터.


임계영역과 쓰레드에 안전한 함수

임계영역 : 두 개 이상의 쓰레드에 의해서 동시에 실행되면 안 되는 영역.

쓰레드 안전한 함수(Thread safe function) : 임계영역에서 호출이 가능한 함수.

예) gethostbyname(쓰레드 불안전한 함수) vs gethostbyname_r(쓰레드 안전한 함수)


gcc -D_REENTRANT thread2.c -o thread2 -lpthread

-D_REENTRANT : #define _REENTRANT의 뜻. 쓰레드 불안전한 함수를 쓰레드 안전한 함수로 바꿔주는 매크로.

-lpthread : 기본적으로 쓰레드와 링크되어 있지 않기 때문에 링크시키기 위한 옵션.


17-3. 임계영역 & 쓰레드의 문제점
컴퓨터가 덧셈을 하는 기본 원리

※ 임시메모리 = accumulator

두 개의 쓰레드에 의한 덧셈 연산
int i = 10;    // 전역 변수
....
i+=10;

※ A Thread가 끝나기 전에 B Thread가 수행될 수 있다.

※ 3단계에서 i=30; 이 되어야 하지만 i=20;이 되어 버렸다.


17-4. 동기화(Synchronization)

임계영역 : 둘 이상의 쓰레드에 의해서 공유되는 메모리 공간에 접근하는 코드 영역.

동시에 접근 가능한 메모리(전역변수)에 접근해서 연산하는 코드 영역

동기화

1. 공유된 메모리에 둘 이상의 쓰레드가 동시 접근하는 것을 막는 행위.

2. 둘 이상의 쓰레드 실행순서를 컨트롤(control)하는 행위.

동기화 기법 : 뮤텍스, 세마포어


17-5. 뮤텍스(Mutex)

뮤텍스의 정의

Mutual Exclusion의 줄이말로 쓰레드 서로 간에 동시 접근을 허용하지 않겠다는 의미.

pthread_mutext_t 타입의 변수를 가리켜 뮤텍스라고 한다.

일종의 (화장실) 문고리에 해당.

뮤텍스의 기본 원리

임계영역에 들어갈 때 뮤텍스(문고리)를 잠그고 들어간다.

임계영역에서 나올 때 뮤텍스를 풀고 나간다.

뮤텍스를 조작하는 함수들

#include<pthread.h>

초기화 : pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

뮤텍스 소멸 : pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex);

뮤텍스 잠금 : pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

뮤텍스 풀기 : pthrea_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);


뮤텍스 동기화 원리


17-6. 세마포어(Semaphore)

세마포어의 정의

sem_t 타입의 변수를 가리켜 흔히 세마포어라 표현한다.

세마포어의 기본 원리

세마포어는 정수를 지닌다. 정수의 값이 0이면 실행 불가를 의미한다.

세마포어가 1이상이 되면 실행 가능을 의미한다.

세마포어는 0미만이 될 수 없지만 1이상은 될 수 있다.

바이너리 세마포어 : 0과 1의 상태만을 지닌다.

세마포어를 조작하는 함수들

#include<semaphore.h>

세마포어 초기화 : sem_init

int sem_init(sem_t *sem, int pshared, unsigned int value);

value : 초기화하고자 하는 값.

세마포어 소멸 : sem_destroy

int sem_destroy(sem_t * sem);

세마포어 감소 : sem_wait

int sem_wait(sem_t * sem);

세마포어 증가 : sem_post

int sem_post(sem_t * sem);


세마포어 동기화 원리

Thread A는 새로운 데이터를 주고 Thread B는 그 데이터를 받는다.

a->b->a->b 순서가 반복된다.


17-7. 쓰레드 기반 서버 구현하기

채팅 서버의 임무 : 전체 클라이언트에게 메세지 내용을 다 보내야 되기 때문에 클라이언트 소켓을 갱신해야 한다.


pthread_mutex_lock(&mutx);

for(i=0;i<clnt_number;i++)

    write(clnt_socks[i], message, len);

pthread_mutex_unlock(&mutx);

위 소스에서 clnt_socks[i]에서 클라이언트 소켓 파일 디스크립터가 존재한다고 생각하고 write함수를 호출하지만 파일 디스크립터가 사라질 위험이 있기 때문에 mutex를 통해 잠가 놓는다.



반응형

+ Recent posts