Data Sharing
volatile int ncount;
void* do_loop(void *loop)
{
while(1)
ncount++;
return NULL;
}
int main(int argc, char *argv[])
{
int status, sec;
pthread_t thread_id;
ncount = 0;
sec = atoi(argv[1]);
status = pthread_create(&thread_id, NULL, do_loop, NULL);
if (status != 0){
perror("pthread_create");
exit(1);
}
sleep(sec);
pthread_cancel(thread_id);
printf("counter = %d\n", ncount);
return 0;
}
+ 같은 precess상의 thread들은 같은 address space를 공유하기 때문에, 데이터들도 서로 공유한다
result
+ 마지막의 출력은 overflow가 발생했음을 알 수 있다
Race Condition
volatile int ncount;
void* do_loop1(void *data)
{
int i;
int *loop = (int *)data;
for(i = 0; i < *loop; i++)
ncount++;
return NULL;
}
void* do_loop2(void *data)
{
int i;
int *loop = (int *)data;
for(i = 0; i < *loop; i++)
ncount--;
return NULL;
}
int main(int argc, char *argv[])
{
void *thread_result;
int status, loop;
pthread_t thread_id[2];
ncount = 0;
loop = atoi(argv[1]);
status = pthread_create(&thread_id[0], NULL, do_loop1, &loop);
status = pthread_create(&thread_id[1], NULL, do_loop2, &loop);
pthread_join(thread_id[0], &thread_result);
pthread_join(thread_id[1], &thread_result);
printf("counter = %d\n", ncount);
return 0;
}
+ 공유하는 데이터를 두 thread가 사용하기 때문에 Race Condition이 발생할 수 있음은 자명하다
result
+ 정상적이라면 (Race Condition이 발생하지 않았다면) ncount를 줄이는 횟수와 ncount를 늘리는 횟수가 같기 때문에 최종 결과값은 0이 나와야합니다
+ 하지만 4, 6번 출력을 보면, 중간에 Race Condition이 발생해 이상한 값이 출력되어있음을 알 수 있습니다
Mutex
- Critical section
- A piece of code that accesses a shared variable
- Must not be concurrently executed by more than one thread
- 공유하는 데이터에 접근하는 코드 조각을 말합니다. 이는 하나 이상의 thread에서 동시에 critical section에 해당하는 코드를 실행해서는 안됩니다.
- Mutual exclusion
- Guarantees that if one thread is executing within the critical section, the others will be prevented from doing so
- critical section을 오직 하나의 thread만이 실행됨을 보장하는 것을 말합니다.
- Mutex
- Primary means of implementing thread synchronization
- thread의 동기화를 위해, Mutual exclusion을 위해 사용되는 데이터입니다.
- 일종의 자물쇠로서 역할을 수행합니다.
Creating a Mutex
- Static initialization
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 기본 mutex의 초기값은 열림상태입니다.
- Dynamic initialization
- int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
- mutex: pointer to mutex variable
- attr: mutex attribution (NULL for default)
- Reaturn value
- 0 if success, otherwise error code
- mutex를 만듭니다.
Destroying a Mutex
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
- mutex: pointer to mutext variable
- Return value
- 0 if success, otherwise error code
- mutex를 삭제합니다.
Locking a Mutex
- int pthread_mutex_lock(pthread_mutex_t *mutex);
- mutex: pointer to mutex variable
- Return value
- 0 if success, otherwise error code
- mutex를 lock하겠다는 뜻은 critical section에 접근하겠다는 뜻입니다.
- 만약 이미 critical section에 누가 들어가있으면 해당 함수 아래의 코드를 실행하지 않고 block혹은 wait상태로, 즉 함수값을 return하지 않고 다른 thread가 unlock할 때까지 기다립니다.
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
- Tries to lock the specified mutex
- Return value
- 0 if succes, otherwise error code
- 위에 일반 lock과는 달리, trylock은 일단 lock을 시도하는데, mutex가 이미 lock이 되어있다면 기다리지 않고 fail값을 return한 후 아래의 코드를 실행합니다.
- mutex가 unlock되어있는 상태였다면, 그냥 lock을 하고 아래의 코드를 실행합니다.
Unlocking a Mutex
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
- Releses the mutex
- If there are threads blocked on the mutex, the scheduling policy shall determine which thread shall acquire the mutex
- 기다리고 있는 thread가 여러개일 경우, unlock을 했을 때, 그 다음으로 lock을 하는 thread는 OS의 스케줄러에 따라서 달라질 수 있다.
- Return value
- 0 if success, otherwise error code
- semaphore의 경우는 flag값을 껏다 키는 권한이 누구에게나 있지만, mutex를 unlock하는 권한은 해당 mutex를 lock했던 개체에 대해서만 있습니다.
Race Condition
volatile int ncount;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *do_loop1(void *data)
{
int i, *loop = (int *)data;
for(i=0; i<*loop; i++){
pthread_mutex_lock(&mutex);
ncount++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *do_loop2(void *data)
{
int i, *loop = (int *)data;
for(i=0; i<*loop; i++){
pthread_mutex_lock(&mutex);
ncount--;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
void *thread_result;
int status, loop;
pthread_t thread_id;
ncount = 0;
loop = atoi(argv[1]);
status = pthread_create(&thread_id[0], NULL, do_loop1, &loop);
status = pthread_create(&thread_id[1], NULL, do_loop2, &loop);
pthread_join(thread_id[0], &thread_result);
pthread_join(thread_id[1], &thread_result);
printf("counter = %d\n", ncount);
pthread_mutex_destory(&mutex);
return 0;
}
+ ncount값을 변경하는 critical section에 두 개 이상의 thread가 접근하려 하는 경우 mutex를 이용해서 mutual exclusion을 지켜주었습니다
result
+ Race Condition이 발생하지 않음을 알 수 있습니다
++ 만약 Signal handler와 Main의 Race Condition을 막기 위해 mutex를 사용한다면 deadlock이 발생할 수도 있다. Main에서 mutex lock을 하고 데이터를 처리하려 하는데, signal이 도착해서 signal handler가 불리고, signal handler에서도 critical section에 접근하기 위해 mutex lock을 하려한다면
++ Main은 signal handler의 종료를 기다리고, Signal handler는 Main의 unlock을 기다리는 교착상태가 발생할 수 있다.
Race Condition (without volatile)
int ncount;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *do_loop1(void *data)
{
int i, *loop = (int *)data;
pthread_mutex_lock(&mutex);
for(i=0; i<*loop; i++)
ncount++;
pthread_mutex_unlock(&mutex);
return NULL;
}
void *do_loop2(void *data)
{
int i, *loop = (int *)data;
pthread_mutex_lock(&mutex);
for(i=0; i<*loop; i++)
ncount--;
pthread_mutex_unlock(&mutex);
return NULL;
}
int main(int argc, char *argv[])
{
void *thread_result;
int status, loop;
pthread_t thread_id;
ncount = 0;
loop = atoi(argv[1]);
status = pthread_create(&thread_id[0], NULL, do_loop1, &loop);
status = pthread_create(&thread_id[1], NULL, do_loop2, &loop);
pthread_join(thread_id[0], &thread_result);
pthread_join(thread_id[1], &thread_result);
printf("counter = %d\n", ncount);
pthread_mutex_destory(&mutex);
return 0;
}
+ 우선 ncount에 volatile이 빠진 경우, Race Condition에 영향을 미치지 않습니다.
+ volatile은 CPU가 register에 값을 저장하는 최적화를 방지하기 위해 설정하는 것입니다. 즉, volatile으로 선언한 변수는 항상 메모리에서 값을 가져온다는 의미입니다. 신선한(?) 메모리에 위치한 변수 값을 가져온다고 해서 Race Condition이 발생하지 않는 것은 아닙니다. 굳이 말하자면 Race Condition이 발생할 확률을 줄여줄 뿐 입니다.
+ 어쨋든, volatile을 선언하지 않아도 왜 Race Condition이 발생하지 않을까요? 이유는 OS에 있습니다. 현대의 OS는 mutex를 lock하거나 unlock할 때 메모리에서 데이터를 가져오는 Assembly code를 으레 집어넣기 때문에 volatile선언이 위 예시에서는 그리 중요하게 작용하지 않습니다.
+ 그리고 mutex lock과 unlock의 위치가 변경되었습니다. 즉, critical section의 범위가 늘어난 것인데, Race Condition의 유무에는 크게 상관이 없습니다.
+ 하지만, 일반적으로 critical section의 범위가 커지면 병렬성이 떨어지는 경향이 있습니다. 어느 한 thread가 독점적으로 실행하는 코드가 길어지면 길어질 수록 다른 thread가 기다리는 시간이 길어지기 때문입니다. 하지만 mutex변경에 대한 overhead는 줄어들게 됩니다.
+ 따라서 프로그래머는 병렬성과 mutex변경에 따른 overheads를 잘 따져 critical section을 설정해야합니다. 하지만 일반적으로는 critical section의 범위를 가능한 작게 유지하는 것이 좋다고 알려져있습니다.
'[학교 수업] > [학교 수업] 시스템 프로그래밍' 카테고리의 다른 글
[시스템 프로그래밍] File I/O (0) | 2024.11.13 |
---|---|
[시스템 프로그래밍] Miscellaneous Thread Topics (1) | 2024.11.12 |
[시스템 프로그래밍] Threads (2) | 2024.10.30 |
[시스템 프로그래밍] Message Queue (3) | 2024.10.17 |
[시스템 프로그래밍] Pipes (3) | 2024.10.15 |