[리눅스] 뮤텍스(mutex)를 이용한 스레드(pthread) 동기화 예제 코드(pthread_mutex_init, pthread_mutex_lock,pthread_mutex_unlock,pthread_mutex_destroy)
컴퓨터/운영체제(주로 리눅스) 2020. 4. 18. 20:08
동기화와 동기화를 이용한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.
https://reakwon.tistory.com/233
임계 영역(Critical Section)
mutex를 알아보기전에 우선 critical section(임계구역)부터 간단하게 알아보자면 critical section은 하나의 한 스레드만이 진입해야하는 특정 코드 구역을 말합니다. 다시 말해 공유자원의 변경이 일어날 수 있는 구간이 임계 영역입니다. 공유자원이라고 하면 여러가지가 있을 수 있는데 간단히 변수라고 생각하세요.
예를 들어볼까요? 자, 아래코드의 임계영역은 cnt=0으로 초기화하며 for루프를 실행하는 구역입니다. 여기에 공유자원은 cnt가 되지요. 스레드가 2개가 있고 차례대로 create하게 됩니다. 아래의 소스코드가 각각 스레드가 실행부가 됩니다. 이 코드의 실행 결과를 한번 예측해보세요.
void *count(void *arg){
int i;
char* name = (char*)arg;
//======== critical section =============
cnt=0;
for (i = 0; i <10; i++)
{
printf("%s cnt: %d\n", name,cnt);
cnt++;
usleep(1);
}
//========= critical section ============
}
우리의 예측은 이렇습니다.
thread1이 count함수 실행 : cnt를 0으로 초기화하고 cnt를 10번 증가시킨 후 종료
thread2가 count함수 실행 : cnt를 0으로 초기화하고 cnt를 10번 증가시킨 후 종료
하지만 실제 결과는 다르지요. 아래와 같이 뒤죽박죽으로 나옵니다.
thread2 cnt: 0
thread1 cnt: 0
thread1 cnt: 1
thread1 cnt: 2
thread2 cnt: 3
thread1 cnt: 4
thread2 cnt: 5
thread1 cnt: 6
thread1 cnt: 7
thread2 cnt: 8
thread2 cnt: 9
thread1 cnt: 10
thread2 cnt: 11
thread1 cnt: 12
thread1 cnt: 13
thread2 cnt: 14
thread1 cnt: 15
thread2 cnt: 16
thread2 cnt: 17
thread2 cnt: 18
뮤텍스(MutEx)
Mutual Exclusion의 약자로 상호배제라고 합니다. 특정 쓰레드 단독으로 들어가야되는 코드 구역에서 동기화를 위해 사용되는 동기화 기법입니다.
우리는 리눅스에서 이 뮤텍스를 통한 동기화를 수행하여 위 코드의 문제점을 해결해볼겁니다.
우선 원래의 문제가 되는 모든 코드는 아래와 같습니다.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int cnt=0;
void *count(void *arg){
int i;
char* name = (char*)arg;
//======== critical section =============
cnt=0;
for (i = 0; i <10; i++)
{
printf("%s cnt: %d\n", name,cnt);
cnt++;
usleep(1);
}
//========= critical section ============
}
int main()
{
pthread_t thread1,thread2;
pthread_create(&thread1, NULL, count, (void *)"thread1");
pthread_create(&thread2, NULL, count, (void *)"thread2");
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
}
문제해결을 위해서 우리가 생각해볼 수 있는 것은 cnt = 0 전에 먼저 실행되는 스레드가 어떤 잠금장치를 이용해 잠그고, 나올때 잠금을 해제하면 되겠다는 생각을 해볼 수 있겠네요. 이런 목적을 달성하기 위해 우리는 4개의 pthread mutex함수를 기억하면 됩니다. 이 함수들은 pthread.h내에 존재합니다.
pthread_mutex_init : mutex를 초기화하는데에는 두 가지 방법이 존재합니다.
1) 정적으로 할당된 뮤텍스를 초기화하려면 PTHREAD_MUTEX_INITIALIZER 상수를 이용해서 초기화합니다.
이런 형식으로 사용합니다. : pthread_mutex_t lock = PTHREAD_MUTX_INITIALIZER;
2) 동적으로 초기화하려면 pthread_mutex_init 함수를 사용하면 됩니다. mutex를 사용하기 전에 초기화를 시작해야합니다.
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
첫번째 인자는 mutex, 두번째인자는 이 mutex의 속정을 주는데, 기본적으로 NULL을 사용합니다.
pthread_mutex_lock, pthread_mutex_unlock : 이 두 함수는 mutex를 이용하여 임계 구역을 진입할때 그 코드 구역을 잠그고 다시 임계 구역이 끝날때 다시 풀어 다음 스레드가 진입할 수 있도록 합니다.
한 가지 중요한 점은 pthread_mutex_lock이 어떤 스레드에서 호출되어 lock이 걸렸을때 다른 스레드가 임계구역에 진입하기 위해서 pthread_mutex_lock을 호출했다면 그 스레드는 이 전의 스레드가 임계 구역을 나올때까지, 즉, pthread_mutex_unlock을 할때까지 기다려야합니다.
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_destroy : 만약 뮤텍스를 동적으로 생성(pthread_mutex_init을 이용하여 초기화)했다면 이 함수를 사용하는 함수가 pthread_mutex_destroy입니다.
int pthread_mutex_destroy(pthread_mutex_t *mutex);
이제 문제를 해결하는 코드를 봐야겠네요.
문제를 해결한 코드는 아래와 같습니다.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
pthread_mutex_t mutex;
int cnt=0;
void *count(void *arg){
int i;
char* name = (char*)arg;
pthread_mutex_lock(&mutex);
//======== critical section =============
cnt=0;
for (i = 0; i <10; i++)
{
printf("%s cnt: %d\n", name,cnt);
cnt++;
usleep(1);
}
//========= critical section ============
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t thread1,thread2;
pthread_mutex_init(&mutex,NULL);
pthread_create(&thread1, NULL, count, (void *)"thread1");
pthread_create(&thread2, NULL, count, (void *)"thread2");
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
}
critical section 전, 후에 lock, unlock을 하는 것과 프로그램 시작 직후, 종료 직전에 mutex를 초기화하고 제거하는 과정만 추가되었습니다.
이제 컴파일하고 실행결과를 보도록 합시다.
#gcc pthread_mutex.c -lpthread
# ./a.out
thread2 cnt: 0
thread2 cnt: 1
thread2 cnt: 2
thread2 cnt: 3
thread2 cnt: 4
thread2 cnt: 5
thread2 cnt: 6
thread2 cnt: 7
thread2 cnt: 8
thread2 cnt: 9
thread1 cnt: 0
thread1 cnt: 1
thread1 cnt: 2
thread1 cnt: 3
thread1 cnt: 4
thread1 cnt: 5
thread1 cnt: 6
thread1 cnt: 7
thread1 cnt: 8
thread1 cnt: 9
차례대로 들어간 스레드부터 0~9까지 출력하는 것을 볼 수 있습니다.
'컴퓨터 > 운영체제(주로 리눅스)' 카테고리의 다른 글
[리눅스] dup, dup2 설명 및 쉬운 사용법, 사용 예제(그림 포함) (3) | 2020.05.21 |
---|---|
[리눅스] 조건변수를 통한 스레드 동기화 설명, 예제(pthread_cond_wait, pthread_cond_signal)와 생산자-소비자 문제 코드 (4) | 2020.04.18 |
[리눅스] 메시지 큐(message queue) 개념과 예제(msgget, msgsnd, msgrcv, msgctl) (1) | 2020.04.10 |
[리눅스] 공유메모리 (Shared Memory) 개념과 예제(shmget, shmat, shmdt, shmctl) (0) | 2020.04.10 |
[리눅스] 세마포어 (Semaphore) 개념과 예제(semget,semctl,semop) (0) | 2020.04.08 |