QNX RTOS: 4-9. Mutexes 실험
2025. 11. 27. 15:00ㆍ운영체제/QNX
전체 코드
/*
* mutex_sync.c
*
* This code is the same as nomutex.c.
*
* The exercise is to use the mutex construct that we learned
* about to modify the source to prevent our access problem.
*
*/
#include <stdio.h>
#include <sys/neutrino.h>
#include <pthread.h>
#include <sched.h>
#include <atomic.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
/*
* The number of threads that we want to have running
* simultaneously.
*/
#define NUMTHREADS 4
/*
* the global variables that the threads compete for.
* To demonstrate contention, there are two variables
* that have to be updated "atomically". With RR
* scheduling, there is a possibility that one thread
* will update one of the variables, and get preempted
* by another thread, which will update both. When our
* original thread runs again, it will continue the
* update, and discover that the variables are out of
* sync.
*
* Note: Error checking has been left out in much of this example
* to increase readability. Production code should not leave out
* this error checking.
*/
pthread_mutex_t myMutex;
pthread_mutex_t var_mutex = PTHREAD_MUTEX_INITIALIZER;
volatile unsigned var1;
volatile unsigned var2;
void *update_thread(void *);
volatile int done;
int main()
{
int ret;
pthread_t threadID[NUMTHREADS]; // a place to hold the thread ID's
pthread_attr_t attrib; // scheduling attributes
struct sched_param param; // for setting priority
int i, policy;
var1 = var2 = 0; /* initialize to known values */
printf("mutex_sync: starting; creating threads\n");
/*
* we want to create the new threads using Round Robin
* scheduling, and a lowered priority, so set up a thread
* attributes structure. We use a lower priority since these
* threads will be hogging the CPU
*/
ret = pthread_getschedparam(pthread_self(), &policy, ¶m);
if ( ret != EOK )
{
fprintf(stderr, "pthread_getschedparam failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
ret = pthread_attr_init(&attrib);
if ( ret != EOK )
{
fprintf(stderr, "pthread_attr_init failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
ret = pthread_attr_setinheritsched(&attrib, PTHREAD_EXPLICIT_SCHED);
if ( ret != EOK )
{
fprintf(stderr, "pthread_attr_setinheritsched failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
ret = pthread_attr_setschedpolicy(&attrib, SCHED_RR);
if ( ret != EOK )
{
fprintf(stderr, "pthread_attr_setschedpolicy failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
param.sched_priority -= 2; // drop priority a couple levels
ret = pthread_attr_setschedparam(&attrib, ¶m);
if ( ret != EOK )
{
fprintf(stderr, "pthread_attr_setschedparam failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
pthread_mutex_init(&myMutex, NULL);
/*
* create the threads. As soon as each pthread_create
* call is done, the thread has been started.
*/
for (i = 0; i < NUMTHREADS; i++)
{
ret = pthread_create(&threadID[i], &attrib, &update_thread, 0);
if ( ret != EOK )
{
fprintf(stderr, "pthread_create failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
}
/*
* let the other threads run for a while
*/
sleep(15);
/*
* Tell the threads to exit.
*/
done = 1;
// wait for them to exit
for (i = 0; i < NUMTHREADS; i++)
{
ret = pthread_join(threadID[i], NULL);
if ( ret != EOK )
{
fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
}
// all other threads are gone, so no need to lock var_mutex for var1 and var2 any longer
printf("all done, var1 is %u, var2 is %u\n", var1, var2);
return EXIT_SUCCESS;
}
/*
* the actual thread.
*
* The thread ensures that var1 == var2. If this is not the
* case, the thread sets var1 = var2, and prints a message.
*
* Var1 and Var2 are incremented.
*
* Looking at the source, if there were no "synchronization" problems,
* then var1 would always be equal to var2. Run this program and see
* what the actual result is...
*/
void do_work()
{
static volatile unsigned var3;
var3++;
/* For faster/slower processors, may need to tune this program by
* modifying the frequency of this printf -- add/remove a 0
*/
if (!(var3 % 1000000))
printf("thread %d did some work\n", pthread_self());
}
void *
update_thread(void *i)
{
while (!done)
{
pthread_mutex_lock(&var_mutex);
if (var1 != var2)
{
printf("thread %d, var1 (%u) is not equal to var2 (%u)!\n", pthread_self(), var1, var2);
var1 = var2;
}
pthread_mutex_unlock(&var_mutex);
/* do some work here */
do_work();
pthread_mutex_lock(&var_mutex);
var1 += 2;
var1--;
var2 += 2;
var2--;
pthread_mutex_unlock(&var_mutex);
}
return (NULL);
}
Case 1, mutex 없음
void *
update_thread(void *i)
{
while (!done)
{
if (var1 != var2)
{
printf("thread %d, var1 (%u) is not equal to var2 (%u)!\n", pthread_self(), var1, var2);
var1 = var2;
}
/* do some work here */
do_work();
var1 += 2;
var1--;
var2 += 2;
var2--;
}
return (NULL);
}
결과
thread 5, var1 (2914231865) is not equal to var2 (2914231866)!
thread 3, var1 (2914246045) is not equal to var2 (2914247188)!
thread 4, var1 (2914262082) is not equal to var2 (2914262084)!
thread 5, var1 (2914314250) is not equal to var2 (2914314249)!
thread 2, var1 (2914333945) is not equal to var2 (2914333945)!
all done, var1 is 2914339021, var2 is 2914339021
Case 2, 부분 Mutex 적용
void *
update_thread(void *i)
{
while (!done)
{
if (var1 != var2)
{
printf("thread %d, var1 (%u) is not equal to var2 (%u)!\n", pthread_self(), var1, var2);
var1 = var2;
}
/* do some work here */
do_work();
pthread_mutex_lock(&var_mutex);
var1 += 2;
var1--;
var2 += 2;
var2--;
pthread_mutex_unlock(&var_mutex);
}
return (NULL);
}
결과
thread 3, var1 (3957726) is not equal to var2 (3957724)!
thread 4 did some work
thread 3 did some work
thread 5, var1 (5698132) is not equal to var2 (5698130)!
thread 5 did some work
thread 3 did some work
all done, var1 is 7531676, var2 is 7531676
Case 3, 전체 Mutex 적용 (do_work 제외)
void *
update_thread(void *i)
{
while (!done)
{
pthread_mutex_lock(&var_mutex);
if (var1 != var2)
{
printf("thread %d, var1 (%u) is not equal to var2 (%u)!\n", pthread_self(), var1, var2);
var1 = var2;
}
pthread_mutex_unlock(&var_mutex);
/* do some work here */
do_work();
pthread_mutex_lock(&var_mutex);
var1 += 2;
var1--;
var2 += 2;
var2--;
pthread_mutex_unlock(&var_mutex);
}
return (NULL);
}
결과
mutex_sync: starting; creating threads
thread 5 did some work
thread 3 did some work
thread 2 did some work
all done, var1 is 3765274, var2 is 3765274
Case 4, 전체 Mutex 적용 (do_work 포함)
void *
update_thread(void *i)
{
while (!done)
{
pthread_mutex_lock(&var_mutex);
if (var1 != var2)
{
printf("thread %d, var1 (%u) is not equal to var2 (%u)!\n", pthread_self(), var1, var2);
var1 = var2;
}
/* do some work here */
do_work();
var1 += 2;
var1--;
var2 += 2;
var2--;
pthread_mutex_unlock(&var_mutex);
}
return (NULL);
}
결과
mutex_sync: starting; creating threads
thread 2 did some work
thread 5 did some work
thread 5 did some work
thread 3 did some work
thread 2 did some work
thread 4 did some work
all done, var1 is 6991708, var2 is 6991708
정리
| 총 loop 횟수 | Miss | 현상 | |
| Case 1, Mutex 없음 | 2,914,339,021 | 거의 대부분 | race condition 그대로 노출 |
| Case 2, 부분 Mutex 적용 (1회) | 7,531,676 | 5%정도? | write와 read가 함께 묶여있지 않기 때문에 Miss가 생김 |
| Case 3, 전체 Mutex 적용 (2회) | 3,765,274 | 없음 | |
| Case 3, 전체 Mutex 적용 (1회) |
6,991,708
|
없음 |
- Mutex 없음
- 루프 수 최댓값, Miss 많음 → 성능은 좋은데 데이터 일관성 완전 깨짐
- 부분 Mutex (락 1~2개, do_work 밖)
- Miss 줄지만 여전히 존재
- 락 오버헤드 + 컨텍스트 스위칭 때문에 루프 수 급감
- 전체 Mutex (do_work 제외, 락 2개)
- Miss 0 (데이터 일관성 보장)
- 하지만 락 2회/루프 → 루프 수 가장 낮음
- 전체 Mutex (do_work 포함, 락 1개)
- Miss 0 유지
- 락 1회/루프로 줄이니, 예상과 달리 루프 수가 2배 이상 증가
- → “락을 잘게 쪼갰다고 항상 빠른 게 아니라,
락 횟수/오버헤드까지 고려해야 한다”는 좋은 반례
'운영체제 > QNX' 카테고리의 다른 글
| QNX RTOS: 4-10. Conditional Variables 실험 (0) | 2025.11.27 |
|---|---|
| QNX RTOS: 4-10. Synchronization - Conditional Variables (0) | 2025.11.27 |
| QNX RTOS: 4-9. Synchronization - Mutexes (0) | 2025.11.26 |
| QNX RTOS: 4-8. Synchronization (Race Condition) (0) | 2025.11.26 |
| QNX RTOS: 4-7. Process Termination and Cleanup (0) | 2025.11.26 |