프로그래밍을 한다는 사람이라면 한번쯤은 세마포어와 뮤텍스에 대해서 들어 보았을 것이다.
대충 공유자원을 관리한다는 것만 알텐데 정확히 이것들은 뭘까?
결론부터 말하자면 공유자원을 관리하는 방식의 차이다.
뮤텍스
뮤텍스(Mutex)는 상호배제(Mutual Exclusion)의 약자다.
일단 상호배제라는 것이 왜 필요한지 알아보자.
아래와 같은 프로그램을 보자.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int num = 0;
void *func(void *t) {
for (int i = 0; i < 100000; i++) {
num = num - 1;
num = num + 1;
}
return NULL;
}
int main() {
pthread_t th[10];
int the;
void *result;
// 10개의 스레드 실행
for (int i = 0; i < 10; i++) {
the = pthread_create(&th[i], NULL, func, NULL);
}
// 10개의 스레드 종료
for (int i = 0; i < 10; i++) {
the = pthread_join(th[i], &result);
}
printf("스레드 연산 결과 num : %d\n", num);
return 0;
}
단순히 10개의 스레드가 전역변수 num에 1을 빼고 1을 더하는 무의미한 연산을 100,000번씩 반복하는 코드다.
결과값은 당연히 0일것이다.
하지만 생각대로 되지 않는다.
결과는 아래와 같이 나왔다.
스레드 연산 결과 num : 23435
...Program finished with exit code 0
Press ENTER to exit console.
이렇게 된 이유는 여러 스레드가 동시에 num 변수를 접근하기 때문이다.
num변수를 공유변수라고 하는데, 동시에 공유변수를 접근하는 것을 충돌이라고 한다.
마치 도로의 교통상황 같다고 생각하면 쉽다.
사거리에 교통을 통제하는 사람이 없어서 마구 충돌이 생기는 것이다.
그럼 어쩌면 좋을까?
한번에 하나의 스레드만 접근할 수 있게 👮경찰을 하나 두자.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int num = 0;
pthread_mutex_t num_lock = PTHREAD_MUTEX_INITIALIZER;
void *func(void *t) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&num_lock);
num = num - 1;
num = num + 1;
pthread_mutex_unlock(&num_lock);
}
return NULL;
}
int main() {
pthread_t th[10];
int the;
void *result;
// 10개의 스레드 실행
for (int i = 0; i < 10; i++) {
the = pthread_create(&th[i], NULL, func, NULL);
}
// 10개의 스레드 종료
for (int i = 0; i < 10; i++) {
the = pthread_join(th[i], &result);
}
printf("스레드 연산 결과 num : %d\n", num);
return 0;
}
num_lock
이라는 mutex을 추가했다.
여기선 mutex는 도로를 통제하는 👮경찰이다.
pthread_mutex_lock
는 num_lock
을 차지하기 위해 기다리고pthread_mutex_unlock
는 num_lock
을 놓아준다.
DeadLock
좋아보이지만 단점도 있다.
두 스레드가 서로 다른 자원을 선점하고 있으면서 선점된 자원을 기다리면 영원히 프로그램이 끝나지 않는다.
A스레드가 a를 선점하고, B스레드가 b를 선점하는데, A는 b를 기다리고 B는 a를 기다리면 끝이 나지 않는다.
이것을 DeadLock이라고 한다.
세마포어
세마포어와 뮤텍스의 차이점은 공유변수에 동시 접근하는 스레드의 수라고 할 수 있다.
예제로 보자. 아래는 공유변수에 동시 접근하는 스레드의 수를 1로 정했다.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
sem_t semaphore;
void *func(void *t) {
int num;
num = *(int*)t;
printf("%d번째 스레드 대기\n", num);
sem_wait(&semaphore);
sleep(1);
printf("%d번째 스레드 지나감\n", num);
sem_post(&semaphore);
return NULL;
}
int main() {
pthread_t th[10];
int the;
void *result;
int nums[10];
// 세마포어 초기화 1
sem_init(&semaphore, 0, 1);
// 10개의 스레드 실행
for (int i = 0; i < 10; i++) {
nums[i] = i+1;
the = pthread_create(&th[i], NULL, func, &nums[i]);
}
// 10개의 스레드 종료
for (int i = 0; i < 10; i++) {
the = pthread_join(th[i], &result);
}
return 0;
}
세마포어는 초기값으로 1이 설정된다.
그 후, func
에서 sem_wait(&semaphore);
를 실행했을때, 0으로 줄어들고sem_post(&semaphore);
를 호출했을 때, 다시 1로 증가한다.
한편 다른 스레드에선 세마포어가 이미 0이기 때문에 sem_wait(&semaphore);
에서 1이 될때까지 기다린다.
이번 예제에선 이해하기 쉽게 1초 동안 기다리도록 했다.
실행결과는 아래와 같다.
이번에는 세마포어의 초기값을 2로 해보자.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
sem_t semaphore;
void *func(void *t) {
int num;
num = *(int*)t;
printf("%d번째 스레드 대기\n", num);
sem_wait(&semaphore);
sleep(1);
printf("%d번째 스레드 지나감\n", num);
sem_post(&semaphore);
return NULL;
}
int main() {
pthread_t th[10];
int the;
void *result;
int nums[10];
// 세마포어 초기화 2
sem_init(&semaphore, 0, 2);
// 10개의 스레드 실행
for (int i = 0; i < 10; i++) {
nums[i] = i+1;
the = pthread_create(&th[i], NULL, func, &nums[i]);
}
// 10개의 스레드 종료
for (int i = 0; i < 10; i++) {
the = pthread_join(th[i], &result);
}
return 0;
}
짠! 두개씩 실행된다!
'프로그래밍 > 운영체제' 카테고리의 다른 글
페이지 교체 알고리즘 (0) | 2022.10.21 |
---|