메뉴 건너뛰기


Developer > Application
5장. 세마포어(Semaphore)
 
세마포어를 사전에서 찾아 보면 "(철도의) 가로대식 신호기"라고 나옵니다. 윈도우즈의 뮤택스에서 이미 신호등 이야기를 했으므로 또 개념이 유사하겠거니 생각하시면 됩니다.  
세마포어는 네덜란드의 이론가 E. W. Dijkstra가 제안한 프로세스 동기화 방법입니다.
세마포어 역시 뮤텍스와 개념은 비슷하지만 가장 중요한 차이점은 뮤텍스는 하나의 공유 자원을 보호하기 위해 사용한다면 세마포어는 제한된 일정 개수를 가지는 공유 자원을 보호할 수 있다는 점이 다릅니다. 예를 들어 화장실에 사용할 수 있는 변기가 하나인 경우는 뮤텍스를 사용하면 되지만 화장실에 변기가 다섯개 있는 경우 뮤텍스만을 사용한다면 뮤텍스를 다섯개 만들어서 사용해야 합니다. 하지만 뮤텍스는 다른칸의 변기에 대해서는 관심이 없기 때문에 각각의 변기의 사용 효율을 높이기 위해서는 별도의 변기 사용 방법을 정해야 합니다. 이런 경우 세마포어를 사용하면 편리합니다. 예가 좀 지저분 한가요? ㅋㅋ 뭐 적절하지 않은 예인지는 모르겠지만 세마포어가 별도로 필요한 이유는 이해 하셨을 거라 생각합니다.
보호하려는 공유자원이 한개인 경우 "binary 세마포어"라고 하고 여러개인 경우 "counting 세마포어"라고 합니다. 이런 관점에서 보면 뮤텍스는 binary 세마포어의 한 형태라고 볼 수도 있겠죠?
 
1. 유닉스의 세마포어
 
유닉스의 세마포어는 크게 두가지가 존재합니다. 하나는 SYSTEM V의 전통적 세마포어 방식이고 또 다른 하나는 POSIX에서 제공하는 세마포어 입니다. SYSTEM V의 세마포어 방식은 전형적인 유닉스 시스템 프로그래밍 책에 주로 나옵니다.  개념 자체는 같지만 사용법도 POSIX의 것보다 SYSTEM V의 것이 좀 심하게 어렵습니다. 오래된 UNIX OS에서 세마포어를 사용하려면 SYSTEM V의 방식을 사용해야 합니다. 하지만 요즘의 대부분의 UNIX/LINUX는 POSIX를 지원하므로 POSIX방식만을 학습하셔도 상관은 없습니다. 앞으로 두개의 방식 모두 설명을 하겠지만 필요한 POSIX방식만 열심히 공부하시고 SYSTEM V방식은 필요할 때 참고 하세요.
 
1) SYSTEM V의 세마포어
 
세마포어는 리소스의 상태를 나타내는 정수형 변수(카운터)를 갖습니다. 이 변수를 s라고 할때 이 변수의 리소스 상태를 변경하기 위해 다음과 같은 원자화된 p와 v연산만을 사용합니다. (참고로 p와 v는 wait과 signal이라는 말의 네델란드어에서 나온 말입니다)
여기서 원자화란 s를 변경할 수 있는 연산(p와v)을 사용하는 프로세스는 한 순간에 오직 하나 뿐이라는 이야기입니다.
 
p(s) { if(s!=0) s의 값을 하나 감소 else p연산을 하며 s가 0이 아닐 때까지 대기 } v(s) { s의 값을 하나 증가 }
 
세마포어는 위의 방법으로 다음을 보장합니다. 이를 세마포어의 불변 특성(unvatiant)이라고 합니다.
(세마포어 초기 값 + v연산들의 수 - p연산들의 수) >=0
 
예를 들어 좀 지저분 하지만 위의 화장실 이야기를 세마포어로 풀어 보죠.
화장실의 변기를 사용하려면 p연산을 수행해야 하구요. 다 사용한 후 문을 열고 나오려면 v연산을 사용해야 한다고 가정해 봅시다.
화장실에 변기가 5개 이므로 s의 초기값을 5로 지정합니다. 어떤 사람(프로세스)이 변기를 사용하기 위해 p연산을 수행합니다. s가 0이 아니므로 s값을 하나 감소하여 4로 만들고 변기를 사용합니다. 두번째 사람도 와서 p연산을 수행합니다. 그러면 s값이 3이 되고 사용하게 됩니다. 이렇게 다섯명이 오면 s는 0이 되고 변기(공유자원)은 모두 사용되게 됩니다. 이때 여섯번째 사람이 옵니다. 그리고 p연산을 사용하려고 하면 s가 0이므로 s의 값이 0이 아닌 값으로 변경될 때 까지 기다려야 겠죠. 그러던 중 아까 화장실에 들어갔던 첫번째 사람이 볼일을 마치고 v연산을 수행합니다. 그러면 s를 1 증가시켜서 1이 되고 여섯번째 사람은 s가 드디어 0이 아닌값이 되었으므로 변기를 사용하게 되고 다시 s는 0이 됩니다. 이때 두번째 사람이 나왔다면 s는 1이 되고... 뭐 이런 식입니다. 쉬운 이야기를 좀 길게 했네요.
 
그럼 실제 SYSTEM V에서 사용하는 세마포어 함수를 보도록 하겠습니다.
 
int semget(key_t key, int nsems, int semflag);
int semctl(int semid, int semnum, int cmd, union semun arg);
int semop(int semid, struct sembuf *sops, size_t nsops);
 
세마포어를 생성하거나 존재하는 세마포어 집합의 확인자(semaphore set identifier)를 얻기 위해 semget()함수를 사용합니다.
세마포어를 초기화 하거나 제거하는 등의 세마포어 제어 함수는 semctl()입니다.
세마포어의 값을 변경하기 위해서는 semop()함수를 사용합니다.
 
이제 각각의 함수를 살펴보도록 하겠습니다.
 
semget()함수는 세마포어를 필요한 갯수 만큼 생성하거나 이미 만들어진 세마포어집합의 확인자(semaphore set identifier)를 반환합니다.
 
- key : 세마포어집합을 구분하기 위해서 지정하는 숫자입니다.
- nsems : 생성할 세마포어의 수입니다. 최대값은 일반적으로 헤더의 SEMMSL 상수로 정의되어 있습니다.
- semflag : 세마포어집합를 생성하는 방법을 지정합니다.
semflag값에 IPC_CREAT 값이 지정되게 되면 같은 key의 세마포어가 커널에 없을 경우 세마포어집합를 생성하게 됩니다. 이미 있는 경우 새로 생성하지는 않고 기존의 세마포어 확인자를 리턴합니다. IPC_CREAT와 IPC_EXCL을 OR로 함께 사용한다면 이미 해당 세마포어가 있는 경우 이 함수는 -1을 리턴하며 실패하게 됩니다. 또한 semflag값에 부가적인 8진 모드의 값이 세마퍼 집합의 허가사항 형태에 마스크로 OR될 수 있습니다. (마치 유닉스에서 파일에 접근 권한을 0x666과 같이 주는 것을 이야기합니다.)
 
이미 이야기 한 바대로 semget()함수는 세마포어 집합 기술자를 리턴합니다. 새로 생성한게 아니라 기존에 있는 세마포어의 기술자를 얻은 경우 nsems값은 무시됩니다. key는 세마포어 집합을 구분하기 위해서 개발자가 임의로 지정하는 값인데 이 값은 다른 의도되지 않은 프로세스에서 사용하지 않는 값이어야 합니다. 그렇다 보니 이값을 몇으로 줘야 할지 아리송 하죠. 이를 위해 IPC_PRIVATE이라는 상수를 사용할 수 있습니다. 이 값을 사용하면 함수내에서 세마포어 집합을 생성할때 현재 커널에서 사용하고 있지 않은 값을 임의로 만들어 주게 됩니다. 이 값을 사용하면 프로세스 간에 공유는 할 수 없겠죠? 또 다른 방법은 ftok()함수를 사용하는 방법이 있습니다. ftok()함수는 다음과 같이 생겼습니다. 
 
key_t ftok( char *pathname, char proj );
 
ftok함수는 접근 가능하고 실제로 존재하는 파일의 경로(pathname)와 프로젝트 식별자(proj)를 이용하여 key_t 타입의 시스템 V IPC 키 값으로 변환하여 줍니다. 단, 같은 파일에 링크된 서로다른 파일은 같은 값을 리턴합니다. 이 함수를 이용한다면 서로 다른 프로세스에서 영구 존재하는 특정한 파일의 경로와 프로젝트를 나타내기위해 개발자가 정한 proj값을 서로 공유하여 세마포어를 프로세스 간의 동기화 방법으로 사용할 수 있겠죠?
 
semget()함수에 대한 설명이 좀 길었네요.
이번에는 semctl()에 대한 설명입니다.
 
semctl()함수는 세마포어 집합 기술자에 존재하는 특정 세마포어를 제어합니다.
semctl()함수는 다음과 같은 아큐먼트를 갖습니다.
 
- semid : semget()함수에 의해 얻어진 세마포어 집합 기술자를 입력합니다.
- semnum : 세마포어 집합 중의 제어하고자 하는 세마포어의 순번(0부터 시작함)
- cmd : 제어하고자 하는 명령
- arg : cmd에 따라 설정하는 값
 
arg에 사용되는 공용체(union)의 일반적인 모습은 다음과 같습니다.
 
        union semun{
                int val;
                struct semid_ds *buf;
                unsigned short int *arrary;
        }arg;
 
공용체 이므로 위의 멤버 변수 중 하나만 사용되게 되는데 이때 어떤 값을 사용해야 하는 지는 cmd값에 의해 달라집니다.
val변수는 cmd에 SETVAL값을 사용할때 사용하게 되구요. IPC_STAT와 IPC_SET을 사용할때는 buf변수를 사용하게 되며 GETALL과 SETALL을 사용할때는 arrary를 사용하게 됩니다.
 
cmd값으로 세마포어를 제어하게 되는데 사용할 수 있는 값이 생각보다 많습니다.
 
IPC_STAT: 집합에 대한 semid_ds 구조를 조회하고, semun union안의 buf 아규먼트의 주소지에 저장한다. 
IPC_SET: 집합에 대한 semid_ds 구조의 ipc_perm 멤버의 값을 지정한다. semum union의 buf 아규먼트로 부터 값을 가져온다. 
IPC_RMID: 커널로 부터 집합을 제거한다. 
GETALL: 집합으로 부터 모든 세마퍼의 값을 얻는데 사용된다. 정수값들이 union의 배열 멤버에 의해 지정된 unsigned short integer 배열에 저장된다. 
GETCNT: 자원에 대해 현재 기다리고 있는 프로세스의 수를 반환한다. 
GETPID: 마지막 semop 호출을 수행한 프로세스의 PID를 반환한다. 
GETZCNT: 100% 자원 활용을 위해 현재 기다리고 있는 프로세스의 수를 반환한다. 
SETALL: 집합안의 모든 세마퍼의 값을 union의 배열 멤버안에 포함된 매칭되는 값으로 지정한다. 
SETVAL: 집합안의 개별적인 세마퍼의 값을 union의 val 멤버의 값으로 지정한다
GETVAL: 집합안의 개별적인 세마퍼의 값을 알아냅니다.
 
자세한 사항은 man으로 직접 살펴 보시구요. 여기서는 자주 사용되는 값만 보도록 하겠습니다.
 
SETVAL값은 해당 세마포어를 arg.val의 값으로 초기화 하기 위해 사용합니다.
IPC_RMID값은 다 사용한 세마포어 집합을 삭제 하기 위해 사용합니다. semnum값은 무시되게 됩니다.
GETVAL은 해당 세마포어의 값을 알아내기 위해 사용합니다. 이때는 arg는 무시되게 됩니다.
 
참고로 semctl()의 arg변수는 cmd값에 의해 필요하지 않은 경우도 있습니다. 그래서 solaris에서는 semctl()함수의 원형이 다음과 같이 되어 있어 생략할 수 있도록 되어 있습니다.
int semctl(int semid, int semnum, int cmd, ...);
하지만 생략하는 것보다는 이식성을 위해 arg의 생략시에는 0을 적어 주는 것이 더 좋을 듯 싶습니다.
 
마지막으로 semop()함수입니다.
 
semop()함수는 세마포어의 값을 변경하기 위해 사용합니다. 사용하는 아큐먼트는 다음과 같습니다.
- semid : 세마포어 집합 기술자를 지정합니다.
- sops : 세마포어의 동작을 나타내는 구조체의 배열을 지정합니다.
- nsops : sops에 지정된 배열의 수를 지정합니다.
 
sops에 사용하는 sembuf라는 구조체는 다음과 같이 생겼습니다.
 
struct sembuf {
                unsigned short  sem_num;        
                short   sem_op;                        
                short   sem_flg;                        
};
즉 세마포어 집합 중 0번째 세마포어에 1을 더하고 싶으면 sem_num에는 0을 지정하고 sem_op에 1을 지정하면 됩니다. 물로 1을 빼고 싶으면 sem_op에 -1을 지정하면 됩니다. 꼭 1씩 하지 않아도 됩니다. 3을 빼고 싶으면 sem_op에 -3을 지정하면 되겠죠? sem_flg는 IPC_NOWAIT, SEM_UNDO 값을 가질 수 있는데 IPC_NOWAIT을 사용하면 블록 상태에 빠지지 않게 되고 SEM_UNDO하면 세마포어가 커널에서 제거될때 이전 모든 동작의 영향을 반대로 하여 세마포어 값을 리셋합니다. 그래서 일반적으로 sem_flg값에 SEM_UNDO를 많이 사용하게 됩니다.
 
이로서 함수에 대한 설명이 모두 끝났네요.
머리가 아프시다구요?  사용예를 보면 좀 괜찮아 지실 겁니다.
 
#include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <sys/ipc.h> #include <sys/sem.h> int cnt=0; static int semid; void SemP() { struct sembuf pbuf; pbuf.sem_num=0; pbuf.sem_op=-1; pbuf.sem_flg=SEM_UNDO; if(semop(semid, &pbuf, 1)==-1) fprintf(stderr, "Error semop()\n"); } void SemV() { struct sembuf vbuf; vbuf.sem_num=0; vbuf.sem_op=1; vbuf.sem_flg=SEM_UNDO; if(semop(semid, &vbuf, 1)==-1) fprintf(stderr, "Error semop()\n"); } void *Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ SemP(); tmp=cnt; usleep(1000); cnt=tmp+1; SemV(); } printf("Thread1 End\n"); return NULL; } void *Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ SemP(); tmp=cnt; usleep(1000); cnt=tmp+1; SemV(); } printf("Thread2 End\n"); return NULL; } int main(int argc, char *argv[]) { pthread_t thread1; pthread_t thread2; union semun{ int val; struct semid_ds *buf; unsigned short int *arrary; }arg; if((semid=semget(IPC_PRIVATE, 1, IPC_CREAT|0666))==-1) fprintf(stderr, "Error semget()\n"); arg.val=1; if(semctl(semid, 0, SETVAL, arg)==-1) fprintf(stderr, "Error semctl()\n"); pthread_create(&thread1, NULL, Thread1, NULL); pthread_create(&thread2, NULL, Thread2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("%d\n", cnt); if(semctl(semid, 0, IPC_RMID, arg)==-1) fprintf(stderr, "Error semget()\n"); return 0; }
 
위의 실행결과도 우리가 매번 봐 왔던 것과 같습니다.
예제에서는 생성한 세마포어 집합에 하나의 세마포어 밖에 없으며 게다가 그 세마포어 역시 바이너리 세마포어여서 마치 뮤텍스와 같이 사용했습니다.
더 많은 세마포어는 여러분이 한번 만들어 보세요.
 
참고로 http://member.hitel.net/~jeanhh/hujin/remote/lpg/lpg_0.html 에 가보니깐 세마포어 랩퍼 함수를 만들어 놓았더군요. 한번 참조해 보세요.
그리고 이곳도 함 참고로 보세요. http://network.hanb.co.kr/view.php?bi_id=1398
 
 
2) POSIX의 세마포어
 
위에서 설명한 SYSTEM V의 세마포어를 보고 혹시나 기가 죽지 않았나 내심 걱정이 됩니다.
하지만 이제 아무 걱정하지 마세요. POSIX의 세마포어는 SYSTEM V의 것 보다 훨씬 쉽고 간단합니다.
POSIX의 세마포어는 우리가 생각하는 상식선에서 구현할 수 있습니다.
용기를 내십시오. ^_^
 
아마도 생각컨데 SYSTEM V의 세마포어가 어려운 것 중의 하나는 세마포어 집합이라는 개념이 있어서 어렵게 하는 것 같습니다. 하지만 POSIX에는 세마포어 집합이라는 개념이 없습니다.
 
자 그럼 POSIX의 세마포어를 함 알아봅시다.
POSIX의 세마포어는 명명된 세마포어(named semaphore)와 명명되지 않은 세마포어(unnamed semaphore)로 나뉩니다.
 
다음은 POSIX 세마포어에서 사용하는 함수의 원형입니다. 
 
sem_t *sem_open(const char *name,  int oflag, mode_t mode , int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
 
int sem_init(sem_t *sem, int pshared, unsigned  value);
int sem_destroy(sem_t *sem);
 
int sem_getvalue(sem_t *sem, int *sval);
 
명명된 세마포어를 생성하거나 생성된 세마포어를 얻어 오기 위해 sem_open()함수를 사용합니다.
세마포어를 사용하기 위해서는 sem_wait()함수를 사용합니다.
세마포어를 다 사용한 후에는 sem_post()함수를 사용합니다.
 
명명되지 않은 세마포어를 만들기 위해서는 sem_init()함수를 사용합니다.
더이상 명명되지 않은 세마포어를 사용하지 않을 거라면 sem_destroy()함수로 생성된 세마포어를 없앱니다.
 
벌써 대충 사용방법이 눈앞에 펼쳐지지 않습니까? 상식선에서 생각하세요. ㅋㅋ
 
sem_open()함수는 명명된 세마포어를 생성하거나 이미 생성된 세마포어의 핸들을 얻어 옵니다.
- name : 세마포어의 이름을 적습니다. 이 이름은 /문자로 시작하는 파일의 경로와 같은 형식으로 지정하여야 합니다. 단, .  .. 은 사용할 수 없습니다.
- oflag : oflag값이 O_CREAT이면 명명된 세마포어가 없다면 세마포어를 생성하고 이미 존재한다면 해당 세마포어의 핸들을 리턴합니다. 이미 세마포어가 존재하는 경우 이 함수의 호출을 실패로 만들고 싶다면 O_EXCL을 O_CREAT와 같이 OR로 설정하면 됩니다.
- mode : 세마포어에 대한 사용 권한의 마스크를 8진수로 지정합니다.
- value : 세마포어의 초기 값을 지정합니다. 이 값에 의해 세마포어의 최대 수락 갯수가 정해 지겠죠? 즉, 공유자원이 몇개 있는지를 지정하는 것입니다.
 
 
이렇게 POSIX에서는 세마포어의 생성 및 초기화를 한큐에 끝내 버리네요.
 
sem_wait()함수는 지정한 세마포어를 사용하기를 요청합니다. 만약 세마포어가 0보다 큰값이라면 이 세마포어의 값을 하나 감소시키고 사용합니다. 만약 0이라면 0보다 큰 값이 될 때까지 대기 하게 됩니다. 아큐먼트로는 세마포어의 핸들을 입력합니다.
 
sem_trywait()함수는 sem_wait()함수와 비슷하게 동작하지만 세마포어의 값이 0인 경우 대기(블록)하지 않고 에러를 리턴하는 것이 다릅니다.
 
sem_pos()함수는 지정한 세마포어를 다 사용하여 이를 놓아 주는 함수입니다. 이때는 세마포어의 값을 하나 증가시키게 됩니다. 아큐먼트로는 세마포어의 핸들을 입력합니다.
 
sem_close()함수는 sem_open()에의해 열린 명명된 세마포어를 닫습니다. 아큐먼트로는 세마포어의 핸들을 입력합니다. 이 함수는 명명된 세마포어에 대한 링크만을 제거합니다. 실제로 명명된 세마포어가 제거되지는 않고, 열어 놓은 세마포어의 핸들만 사용 할 수 없게 됩니다.
 
sem_unlink()함수는 이름있는 세마포어를 제거하는 함수입니다. 아큐먼트로는 세마포어의 이름을 줍니다.
 
 
sem_init()함수는 명명되지 않은 세마포어를 초기화(생성)하는 함수입니다.
아큐먼트로는 세마포어 핸들을 받아올 sem과 임의의 프로세스의 쓰레드들 사이에 혹은 프로세스들 사이에 세마포어가 공유되는지 어떤지를 나타내는 pshared(만약 pshared 값이 0이라면 세마포어는 프로세스의 쓰레드들 간에 공유된다. Pshared가 0이 아니면 세마포어는 프로세스 간에 공유된다.) 그리고 세마포어가 초기값으로 가지는 값 value를 입력합니다. 이름없는 세마포어는 초기화 되자마자 사용할 수 있으며 세마포어에 대한 lock과 unlock 동작은 sem_wait()와 sem_post()함수를 사용하면 됩니다.
sem_destroy()함수는 이름 없는 세마포어를 제거하는 함수입니다.
 
현재 세마포어의 값을 알아오고 싶다면 sem_getvalue()함수를 사용하면 됩니다.
 
참고로 HPUX에서는 이들 POSIX 세마포어 관련 함수를 사용하기 위해 링크시에 -lrt를 사용해서 리얼타임 라이브러리를 링크해야 합니다.
 
 
 
 
이번에는 예제입니다.
예제는 모두 같으니깐... 뭐 설명은 생략하겠습니다.
 
 
#include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <semaphore.h> int cnt=0; static sem_t hsem; void *Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ sem_wait(&hsem); tmp=cnt; usleep(1000); cnt=tmp+1; sem_post(&hsem); } printf("Thread1 End\n"); return NULL; } void *Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ sem_wait(&hsem); tmp=cnt; usleep(1000); cnt=tmp+1; sem_post(&hsem); } printf("Thread2 End\n"); return NULL; } int main(int argc, char *argv[]) { pthread_t thread1; pthread_t thread2; if(sem_init(&hsem, 0, 1)<0){ fprintf(stderr, "Semaphore Initilization Error\n"); return 1; } pthread_create(&thread1, NULL, Thread1, NULL); pthread_create(&thread2, NULL, Thread2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("%d\n", cnt); sem_destroy(&hsem); return 0; }
 
SYSTEM V 방식보다는 훨씬 쉽죠?
 
참고로 이곳의 글도 함 참고로 보세요. http://network.hanb.co.kr/view.php?bi_id=1399
 
지금까지 유닉스/리눅스에서 제공하는 세마포어에 대해서 알아 보았습니다. 
 
 
 
2. 윈도우즈의 세마포어
 
글을 몇일에 거쳐 작성하다가 보니깐 점점 길어지고 또 점점 더 읽기 싫은 글이 되어 가고 있는 것 같네요. 쩝~
 
그래도 또 가봅시다.
 
이번에 윈도우즈에서 구현하는 세마포어 입니다.
앞의 윈도우즈 뮤텍스를 읽어보셨다면 함수자체는 뭐 사용법이 비슷합니다.
 
윈도우즈의 뮤텍스 관련된 함수는 다음과 같은 것들이 있습니다.
 
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
 
세마포어를 만들기 위해서는 CreateSemaphore(), 이미 존재하는 세마포어의 핸들을 얻기 위해서는 OpenSemaphore() 그리고 세마포어를 다 사용한 후 세마포어의 소유권을 넘겨줄때는 ReleaseSemaphore()를 호출하면 됩니다.
이미 이야기 한대로 세마포어의 값을 변화시키는 함수는 커널용 대기함수들(WaitForMultipleObjects(), WaitForSingleObject())을 사용하면 됩니다. 또한 생성한 세마포어를 파괴시킬때는 CloseHandle()함수를 사용합니다.
 
대기함수들은 세마포어의 값을 하나 감소시킵니다.
 
CreateSemaphore()함수의 아큐먼트는 다음과 같습니다.
- lpSemaphoreAttributes: 세마포어의 보안 속성을 지정하는 것으로서 주로 상속관계를 지정하기 위해 사용됩니다. 일반적으로는 NULL을 입력합니다.
- lInitialCount : 세마포어의 초기값을 입력합니다.
- lMaximumCount:  세마포어의 최대값을 입력합니다. 특수한 경우가 아니라면 lInitialCount와 lMaximumCount의 값은 같은 값으로 지정합니다.
- lpName : 세마포어에 이름을 문자열로 지어 줍니다. 이름이 지정된 세마포어(명명된 세마포어)를 만듦으로서 이 이름을 아는 다른 프로세스와의 동기화를 할 수 있습니다. 하나의 프로세스에서만 동기화 방식으로 세마포어를 사용한다면 NULL을 입력하여 이름을 지어 주지 않을 수도 있습니다.
 
CreateSemaphore()함수는 생성한 세마포어의 핸들을 리턴합니다. 만약 생성하려는 세마포어의 이름으로 이미 세마포어가 생성되어 있는 경우는 해당 세마포어의 핸들을 리턴하고 GetLastError()로는 ERROR_ALREADY_EXISTS값을 얻을 수 있습니다.
 
OpenSemaphore()함수의 아큐먼트는 다음과 같습니다.
- dwDesiredAccess: 접근속성
- bInheritHandle : 상속옵션
- lpName : 지정된 세마포어의 이름
 
OpenSemaphore()함수는 이름이 지정된 세마포어의 핸들을 리턴합니다. 만약 지정된 이름의 세마포어가 없는 경우는 NULL을 리턴하고 GetLastEror()함수는 ERROR_FILE_NOT_FOUND값을 리턴합니다.
 
ReleaseSemaphore()함수는 잡았던 세마포어의를 다시 놓아주는(세마포어의 값을 정해진 값 만큼 증가 시키는) 함수입니다.
- hSemaphore : 대상이 되는 세마포어의 핸들을 입력합니다.
- lReleaseCount : 세마포어의 값을 변경시키고자하는 값의 크기를 입력합니다.
- lpPreviousCount :   변경되기전의 세마포어의 값을 담아 줍니다.
 
이제 예제만 보면 되겠네요.
 
예제를 보기전에 참고로...
유닉스에서는 세마포어의 현재값을 알아내는 semget()이라는 함수가 있습니다.
그런데 윈도우즈에서는 그러한 값이 존재하지 않습니다.
 
하지만 ReleaseSemaphore()함수를 호출하면 이전의 세마포어의 값을 리턴하기 때문에 이를  이용하면 현재 세마포어의 값을 알아낼 수 있습니다.
다음은 ReleaseSemaphore()함수를 이용해서 현재 세마포어의 값을 리턴하는 함수를 함 만들어 보았습니다.
int SemaphoreCount(HANDLE hSem) { DWORD result; LONG count; result = WaitForSingleObject(hSem, 0); if(result==WAIT_TIMEOUT) return 0; ReleaseSemaphore(hSem, 1, &count); return count+1; }
 
다음은 위의 윈도우즈에서 세마포어함수를 사용한 예제입니다.
 
#include <stdio.h> #include <process.h> #include <windows.h> int cnt=0; HANDLE hSem; DWORD WINAPI Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hSem, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; ReleaseSemaphore(hSem, 1, NULL); } printf("Thread1 End\n"); return 0; } DWORD WINAPI Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hSem, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; ReleaseSemaphore(hSem, 1, NULL); } printf("Thread2 End\n"); return 0; } int main(int argc, char *argv[]) { HANDLE hThread[2]; hSem=CreateSemaphore(NULL, 1, 1, NULL); hThread[0]=CreateThread(NULL, 0, Thread1, NULL, 0, NULL); hThread[1]=CreateThread(NULL, 0, Thread2, NULL, 0, NULL); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); printf("%d\n", cnt); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hSem); return 0; }
 
 
다른 예제와 같으므로 예제에 대한 설명은 패스...


지금까지 세마포어에 대해 알아보았는데 세마포어는 뮤테스와는 달리 공유자원을 정해진 수의 쓰레드나 프로세스가 접근할 수 있다는 것입니다. 그런데 지금까지의 예제는 뮤텍스와 동일한 역활을 하는 것만 보였습니다.
다중 쓰레드에의해 세마포어를 정해진 수 만큼 사용하는 활용예는 추후 만들어서 제시하도록 하겠습니다.
 
세마포어에 대한 이야기는 여기까지 입니다.
 
하나의 주제의 글을 한번에 완성하지 못하고 계속 나눠쓰게 되네요.
빨리 빨리 글이 올라오지 않더라도 느긋하게 기다려 주세요. ^^
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com