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, &param);
	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, &param);
	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배 이상 증가
    • → “락을 잘게 쪼갰다고 항상 빠른 게 아니라,
      락 횟수/오버헤드까지 고려해야 한다”는 좋은 반례