뮤택스 세마포어
1. 뮤텍스 기본 정의
뮤텍스는 한 번에 하나의 쓰레드만이 실행되도록 하는 재 입장할 수 있는 코드 섹션에 직렬화된 접근이 가능하게 할 때 사용됩니다. 뮤텍스 객체는 제어되는 섹션에 하나의 쓰레드만을 허용하기 때문에 해당 섹션에 접근하려는 다른 쓰레드들을 강제적으로 막음으로써 첫 번째 쓰레드가 해당 섹션을 빠져나올 때까지 기다리도록 합니다.
뮤텍스는 화장실에 들어가기 위한 열쇠로 비유할 수 있습니다. 즉 화장실에 들어갈 수 있는 열쇠를 한 사람이 갖고 있다면, 한 번에 열쇠를 갖고 있는 그 한 사람만이 들어갈 수 있습니다. 화장실에 열쇠를 갖고 있는 사람이 들어가 볼일을 다 본 후에는 줄을 서서 기다리고 있는(대기열-큐) 다음 사람에게 열쇠를 주게 됩니다.
세마포어는 공유 리소스에 접근할 수 있는 최대 허용치만큼 동시에 사용자 접근을 할 수 있게 합니다. 쓰레드들은 리소스 접근을 요청할 수 있고 세마포어에서는 카운트가 하나씩 줄어들게 되며 리소스 사용을 마쳤다는 신호를 보내면 세마포어 카운트가 하나 늘어나게 됩니다.
뮤텍스는 값이 1인 세마포어입니다.
세마포어는 빈 화장실 열쇠의 갯수라고 보면 됩니다. 즉, 네 개의 화장실에 자물쇠와 열쇠가 있다고 한다면 세마포어는 열쇠의 갯수를 계산하고 시작할 때 4의 값을 갖습니다. 이 때는 이용할 수 있는 화장실 수가 동등하게 됩니다. 이제 화장실에 사람이 들어갈 때마다 숫자는 줄어들게 됩니다. 4개의 화장실에 사람들이 모두 들어가게 되면 남은 열쇠가 없게 되기 때문에 세마포어 카운트가 0이 됩니다. 이제 다시 한 사람이 화장실에서 볼일을 다 보고 나온다면 세마포어의 카운트는 1이 증가됩니다. 따라서 열쇠 하나가 사용가능하기 때문에 줄을 서서 기다리고 있는 다음 사람이 화장실에 입장할 수 있게 됩니다.
2. 언제 사용하나요?
Process 혹은 Thread 간의 communication / 통신 시에 shared memory (공유 메모리) 등을 쓰는 경우에 하나의 자원 (Resource or memory or even files) 에 두 개 이상의 process or thread 가 write 을 하는 경우에 문제가 발생한다. 이를 방지하기 위해 하나의 process 혹은 thread 만이 resource 에 접근하여 write 을 할 수 있도록 제어해 주어야 하고, 이 때에 Mutex 혹은 Semaphore 를 사용한다. Thread 에서는 MuTex 를 사용하고, Process 에서는 Semaphore 를 사용한다.
3. 뮤텍스 상세
공유 자원에 대한 접근 제어
- global int count = 0
- A 쓰레드가 count값 0을 읽는다.
- B 쓰레드가 count값 0을 읽는다.
- A 쓰레드가 카운팅 연산을 하기 전에 B쓰레드가 접근해 버린 상황이다.
- A 쓰레드가 count+1 연산을 하고 값을 쓴다. count = 1
- B 쓰레드가 count+1 연산을 하고 값을 쓴다. count = 1
- count 값을 읽어와서
- 카운팅 연산을 하고
- 연산 결과를 저장하는
- global int count = 0;
- A 쓰레드가 임계 영역에 진입
- B 쓰레드도 임계 영역 진입을 시도하지만, A 쓰레드가 진입해 있으므로 임계 영역 밖에서 대기한다.
- A 쓰레드가 count 값을 읽고
- 카운팅 연산을 해서
- 값을 저장한다. count = 1
- A 쓰레드가 임계 영역에서 빠져나온다.
- 비로서 B 쓰레드가 임계 영역에 진입해서
- count 값 1을 읽어서 카운팅 연산을 하고 저장한다. count = 2
Mutex
global int v = 1; lock() { while(1) { if (v==1) break; } v = 0; return 1; } unlock() { v = 1; break; }
- Atomicity - mutex 잠금(lock)는 최소단위 연적(atomic operation) 으로 작동한다. 이말의 뜻은 하나의 쓰레드가 mutex 를 이용해서 잠금을 시도하는 도중에 다른 쓰레드가 mutex 잠금을 할수없도록 해준다는 뜻이다. 한번에 하나의 mutex 잠금을 하도록 보증해준다.
- Singularity - 만약 스레드가 mutex 잠금을 했다면, 잠금을 한 쓰레드(:12)가 mutex 잠금을 해제 하기 전까지 다른 어떠한 쓰레드도 mutex 잠금을 할수 없도록 보증해준다.
- Non-Busy Wait - 바쁜대기 상태에 놓이지 않는다는 뜻으로, 하나의 쓰레드가 mutex 잠금을 시도하는데 이미 다른 쓰레드가 mutex 잠금을 사용하고 있다면 이쓰레드는 다른 쓰레드가 락을 해제하기전까지 해당 지점에 머물러 있으며 이동안은 어떠한 CPU 자원도 소비하지 않는다 (이를테면 sleep).
뮤텍스 만들기
pthread_mutex_t a_mutex = PTHREAD_MUTEX_INITIALIZER;
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutex_attr *attr);;;;
뮤텍스 잠금, 잠금해제, 제거
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_destory(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);다음은 쓰레드간 공유되는 자원을 위해서 잠금을 어떻게 사용하는지를 보여주는 간단한 예제다.
#include <stdio.h> #include <unistd.h> #include <pthread.h> int ncount; // 쓰레드간 공유되는 자원 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 쓰레드 초기화 void* do_loop(void *data) { int i; for (i = 0; i < 10; i++) { pthread_mutex_lock(&mutex); // 잠금을 생성한다. printf("loop1 : %d\n", ncount); ncount ++; if(i == 10) return; pthread_mutex_unlock(&mutex); // 잠금을 해제한다. sleep(1); } } void* do_loop2(void *data) { int i; // 잠금을 얻으려고 하지만 do_loop 에서 이미 잠금을 // 얻었음으로 잠금이 해제될때까지 기다린다. for (i = 0; i < 10; i++) { pthread_mutex_lock(&mutex); // 잠금을 생성한다. printf("loop2 : %d\n", ncount); ncount ++; pthread_mutex_unlock(&mutex); // 잠금을 해제한다. sleep(2); } } int main() { int thr_id; pthread_t p_thread[2]; int status; int a = 1; ncount = 0; thr_id = pthread_create(&p_thread[0], NULL, do_loop, (void *)&a); sleep(1); thr_id = pthread_create(&p_thread[1], NULL, do_loop2, (void *)&a); pthread_join(p_thread[0], (void *) &status); pthread_join(p_thread[1], (void *) &status); status = pthread_mutex_destroy(&mutex); printf("code = %d", status); printf("programing is end"); return 0; }위의 코드를 우선 mutex 잠금을 하지 않은체 컴파일후 실행해보자. 간단하게 pthread_mutext_lock 와 pthread_mutex_unlock 부만 주석처리하면 된다. 그러면 do_loop2 와 do_loop 가 일정한 간격을 두고 ncount 자원에 접근하는 것을 볼수 있을것이다. 그러나 우리는 do_loop 가 ncount 자원을 접근하고 있는동안 다른 쓰레드가 접근하지 않기를 원할때가 있을것이다. 이럴때 뮤텍스 잠금을 사용하면 된다.
“MUTual EXclusion 으로 상호배제라고도 한다. Critical Section을 가진 스레드들의 Runnig Time이 서로 겹치지 않게 각각 단독으로 실행되게 하는 기술이다. 다중 프로세스들의 공유 리소스에 대한 접근을 조율하기 위해 locking과 unlocking을 사용한다. 즉, 쉽게 말하면 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다는 의미이다.” (from reference #1)
“제어되는 섹션에 하나의 쓰레드만을 허용하기 때문에 해당 섹션에 접근하려는 다른 쓰레드들을 강제적으로 막음으로써 첫 번째 스레드가 해당 섹션을 빠져나올 떄 까지 기다린다. 예) Niclas Winquits씨가 2005년에 쓴 화장실 비유 뮤텍스는 화장실에 들어가기 위한 열쇠로 비유할 수 있다. 즉 화장실에 들어갈 수 있는 열쇠를 한 사람이 갖고 있다면 한번에 그 한 사람만 들어 갈 수 있다. 화장실에 들어간 사람이 나오면 줄을 서서 기다리는 다음 사람(대기열-큐)에게 열쇠를 주게 된다.” (from reference #2)
Mutex Implementation in Java (from reference #4)
public Class MyClass {
private static Object lock = new Object();
public void myMethod() {
// Stuff that multiple threads can execute simultaneously.
synchronized(MyClass.lock) {
// Stuff that only one thread may execute at a time.
}
}
}
MyClass 의 instance 들은 lock 이라는 object 를 공유하고 (note ‘static’), shared resource 에 write 을 하기 이전에 test and set (lock 이 free 한지 test 해보고, free 하다면 lock 을 set 하고 critical section 에 진입, critical operation 을 마치고 free lock) 을 한다. Java 의 synchronized keyword 는 thread-safe 를 guarantee 한다. (후에 Java Synchronized keyword 관련 포스팅 요망)
Semaphore 란?
” 세마포어는 리소스의 상태를 나타내는 간단한 카운터로 생각할 수 있다. 일반적으로 비교적 긴 시간을 확보하는 리소스에 대해 이용한다. 유닉스 시스템의 프로그래밍에서 세마포어는 운영체제의 리소스를 경쟁적으로 사용하는 다중 프로세스에서 행동을 조정하거나 또는 동기화 시키는 기술이다. 세마포어는 운영체제 또는 커널의 한 지정된 저장장치 내 값으로서, 각 프로세스는 이를 확인하고 변경할 수 있다. 확인되는 세마포어의 값에 따라, 그 프로세스가 즉시 자원을 사용할 수 있거나, 또는 이미 다른 프로세스에 의해 사용 중이라는 사실을 알게 되면 재시도하기 전에 일정 시간을 기다려야만 한다. 세마포어는 이진수 (0 또는 1)를 사용하거나, 또는 추가적인 값을 가질 수도 있다. 세마포어를 사용하는 프로세스는 으레 그 값을 확인하고, 자원을 사용하는 동안에는 그 값을 변경함으로써 다른 세마포어 사용자들이 기다리도록 해야한다. ” (from reference #1)
” 공유 리소스에 접근할 수 있는 최대 허용치만큼 동시에 사용자 접근을 할 수 있게 한다. 쓰레드들은 리소스 접근 요청을 할 수 있고 세마포어에서는 카운트가 하나씩 줄어들게 되며 리소가 모두 사용 중 인경우(카운트 0) 다음 작업은 대기를 하게 된다. 리소스 사용을 마쳤다는 신호를 보내면 카운트가 하나 늘어나게 되고 다음 작업이 사용 할 수 있다. 예) 세마포어는 빈 화장실 열쇠의 갯수에 비유할 수 있다. 즉 비어있는 칸 만큼 열쇠가 있다고 가정하면 사람들이 화장실에 들어갈 때 마다 열쇠의 숫자는 줄어 들게 된다. 화장실 칸이 다 찰 경우 카운트는 0이 되며 다음 사람은 줄을 서서 기다린다. 볼일을 끝내고 나오면 리소스 사용을 마쳣다는 신호로 카운트를 하나 늘이고 다음 사람에게 부여 한다. ” (from reference #2)
Semaphore Implementation in Java (needs to be updated)
- Mutex 의 구현과 유사하다. Mutex 의 경우 0과 1의 두 상태 뿐인 lock 인 대신, Semaphore 의 경우 counter 여야 하고, lock 이 true 인지 확인하는 대신 counter 가 0 혹은 max # of threads allowed (구현 방식에 따라 min or max 를 취하는 방식) 인지를 확인하여, test and set 으로 critical section 에 진입하면 counter 를 1 증가 혹은 감소시키고, shared resource 에 write 하는 등의 작업이 끝난 뒤 critical section 을 exit 하면서 counter 를 되돌린다.
e) Mutex and Semaphore
- critical section 에서 (즉, mutex 의 lock 가지고 있거나 semaphore 의 counter 를 하나 소비 중인) processing time 이 오래 걸리는 경우는 어떻게 처리하나 -> time-out 을 설정하여 이를 관리한다. (추가 조사 + 포스팅 필요)
f) Mutex vs Semaphore (from reference #1)
- 세마포어는 뮤텍스가 될 수 있지만 뮤텍스는 세마포어가 될 수 없다. (mutex 는 상태가 0, 1 두 개 뿐인 binary semaphore 이다)
- 세마포어는 소유할 수 없는 반면 뮤텍스는 소유가 가능하며 소유주가 이에 대한 책임을 진다. (mutex 의 경우 상태가 두개 뿐인 lock 이므로 lock 을 ‘가질’ 수 있다.)
- 뮤텍스의 경우 뮤텍스를 소유하고 있는 쓰레드가 이 뮤텍스를 해제할 수 있다. 하지만 세마포어의 경우 이러한 세마포어를 소유하지 않는 스레드가 세마포어를 해제할 수 있다.
- 세마포어는 시스템 범위에 걸쳐있고 파일시스템상의 파일 형태로 존재한다. 반면 뮤텍스는 프로세스 범위를 가지며 프로세스가 종료될 때 자동으로 clean up된다.
- ★★★ 가장 큰 차이점은 관리하는 동기화 대상이 갯수이다. 뮤텍스는 동기화 대상이 오직 하나뿐일 때, 세마포어는 동기화 대상이 하나 이상일 때 사용한다.
http://www.gpgstudy.com/forum/viewtopic.php?t=6198&highlight=%BC%BC%B8%B6%C6%F7%BE%EE
http://crack-tech-interview.com/2014/01/26/semaphore-%EC%99%80-mutex-mutual-exclusion/