동기화, 조건 변수 등 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

조건 변수

조건 변수를 설명하기 전에 다음과 같은 상황이 발생했다고 칩시다.

먼저 스레드 2개가 존재합니다. 저는 짧은 코드를 좋아하므로 아주 간단한 역할을 하는 2개의 쓰레드를 생성했습니다. 

Thread1 Thread2

data값 +1 증가

값을 출력


이때 thread2는 항상 thread1이 data값을 바꾼 다음에만 출력해야되는 조건이 있다면 이런 상황을 어떻게 구현해야할까요?

 

첫번째 방법은 우선 thread2는 항상 data가 변경되는 것을 지속적으로 감시한 후에 출력하면 되겠죠. 이런 해결 방법을 busy-waiting 또는 spinning이라고 합니다. 바쁜 대기라는 것입니다. 하는 일은 없는데 바쁘게 기다리고 있는 상황입니다. 무한루프로 변경을 감지하게 되는 것이라서 CPU의 점유율을 차지하게 됩니다.

 

그렇지 않고 특정 조건이 발생했을때 signal을 보내서 감지할 수도 있습니다. 이때 사용하는게 바로 조건 변수입니다. 

 

우리는 위와 같은 상황을 코드로 구현하기 전 우리가 사용해야하는 도구들을 먼저 간단히 보도록 하겠습니다.

 

pthread_cond_init

조건 변수를 초기화합니다. 이 함수말고 정적으로 조건 변수를 초기화할 경우에는 PTHREAD_CONT_INITIALIZER 상수를 이용해서 초기화할 수도 있습니다.

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

cond라는 조건 변수를 초기화하는데 attr로 속성을 지정할 수 있습니다. NULL이면 기본 조건 변수를 사용합니다.

 

pthread_cond_wait

조건이 참이 될 때까지 대기하는 함수입니다. pthread_cond_wait의 두번째 인수는 조건 변수를 보호하기 위한 뮤텍스입니다. pthread_cond_wait을 호출하기 전에 전달할 mutex를 이용하여 잠근 후에 이 함수를 호출해야합니다. 즉, pthread_cond_wait전에 pthread_mutex_lock을 호출하는데 둘의 mutex는 같아야한다는 것입니다. 

그러면 이 함수는 호출한 스레드를 조건의 발생을 대기하는 스레드들의 목록에 추가하고 뮤텍스를 풀게됩니다.

int pthread_cond_wait( pthread_cond_t* cond, pthread_mutex_t* mutex );

 

여기서 첫번째 인자가 condition 변수이고, 두번째 인자는 동기화를 할mutex입니다.

 

pthread_cond_signal

대기 중인 스레드에게 signal을 보냅니다. 현재 pthread_cond_wait으로 대기중인 스레드를 깨우게 되어 다른 스레드가 이후의 작업을 진행할 수 있도록 해줍니다. pthread_cond_wait과는 다르게 mutex를 받지 않음을 보세요.

int pthread_cond_signal(pthread_cond_t *cond);

 

이제 이것을 바탕으로 위의 문제점을 해결해보도록 해보지요. 아래의 소스 코드가 그것입니다.

코드 설명 : 아래의 코드는 1초마다 data라는 변수의 값을 증가시키는 thread1과 그 값을 단순히 출력해주는 thread2가 존재합니다. 

thread1은 값을 증가할때마다 thread2에게 출력을 하라고 pthread_cond_signal로 신호를 보냅니다.

data는 공유자원이기 때문에 mutex를 사용했습니다. 여기서 데이터의 조작은 한쪽에서 이루어지긴하지만 공유자원의 조작이 여러곳에서 이루어질 수도 있기 때문에 mutex로 동기화 처리를 하였습니다. 

 

쓰레드 동기화 기법에 대한 설명은 지난 포스팅을 참고하시기 바랍니다.

#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

int data=0;

void *increase(void *arg){
        while(1){
                pthread_mutex_lock(&mutex);
                pthread_cond_signal(&cond);
                data++;
                pthread_mutex_unlock(&mutex);
                sleep(1);
        }
}

void *printData(void *arg){
        while(1){
                pthread_mutex_lock(&mutex);
                pthread_cond_wait(&cond,&mutex);
                printf("data :%d\n",data);
                pthread_mutex_unlock(&mutex);
        }
}
int main()
{
    pthread_t thread1,thread2;

    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_create(&thread1, NULL, increase,NULL);
    pthread_create(&thread2, NULL, printData,NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

 

이제 아래의 명령으로 컴파일 후 실행 결과를 보도록 하겠습니다. 결과를 보게되면 1초마다 값이 증가하는 것을 볼 수 있습니다.

# ./a.out
data :1
data :2
data :3
data :4
data :5

 

생산자-소비자 예제

한가지 예제를 더 살펴보도록 합시다. 생산자-소비자의 예제를 조건변수를 사용하여 구현한 코드입니다.

 

코드 설명:

코드가 길어보이지만 큐(queue)라는 자료구조를 아신다면 코드의 절반은 큐의 구현입니다. 그 중에서도 원형큐죠?

 

producer, 생상자 - item을 생성하여 queue에 집어넣습니다. 아래 produce함수를 실행하는 thread이지요.

consumer, 소비자 - item을 큐에서 꺼내어 출력합니다. consume함수를 실행하는 thread입니다.

공통 -현상을 명확히 보기 위해 양쪽 스레드는 랜덤한 시간을 기다렸다가 생산, 소비하게 됩니다.

 

만약 큐가 비어져있으면 소비할 item이 없으니 생산자에게 아이템을 생산하라는 신호를 보냅니다. c_cond가 바로 이 조건 변수입니다.

만약 큐가 전부 다 찼다면 생상할 수 없으니 소비자에게 소비하라는 신호를 보냅니다. p_cond가 바로 그 조건 변수입니다. 

 

코드는 아래와 같습니다.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>

#define Q_MAX       15 + 1

// ================ queue implementation =======================
typedef struct queue_t {
        int             data[Q_MAX];
        int             nitem;
        int             head;
        int             tail;

        pthread_mutex_t mutex;
        pthread_cond_t  c_cond;
        pthread_cond_t  p_cond;

}queue_t;

queue_t queue={
        .p_cond=PTHREAD_COND_INITIALIZER,
        .c_cond=PTHREAD_COND_INITIALIZER,
        .mutex=PTHREAD_MUTEX_INITIALIZER

};

int is_empty( ){
        return queue.nitem == 0;
}

int is_full( ){
        return queue.nitem ==  Q_MAX-1;
}

void put( int d ){

        queue.data[queue.head] = d;
        queue.head = (queue.head+1)%Q_MAX;
        queue.nitem++;
}

int get(){

        int temp =  queue.data[queue.tail];
        queue.tail = (queue.tail+1)%Q_MAX;
        queue.nitem--;
        return temp;
}

// ==============queue implementation end ====================

void * produce(void *arg){

        int i=0;
        while(1){

                pthread_mutex_lock(&queue.mutex);

                if(is_full()){
                        pthread_cond_wait(&queue.c_cond,&queue.mutex);
                }

                printf("produce:%d\n",i);

                put(i);
                pthread_cond_signal(&queue.p_cond);

                pthread_mutex_unlock(&queue.mutex);
                i++;

                if(i==100) break;

                usleep(rand()%1000);
        }
        return 0;

}



void * consume(void *arg){
        while(1){

                pthread_mutex_lock(&queue.mutex);

                if(is_empty()){
                        pthread_cond_wait(&queue.p_cond,&queue.mutex);
                }
                int item=get();

                pthread_cond_signal(&queue.c_cond);
                printf("\t\tconsume:%d\n",item);

                pthread_mutex_unlock(&queue.mutex);

                usleep(rand()%1000);
        }

        return 0;

}



int main(int argc, char **argv){

        int     n;
        pthread_t producer, consumer;
        srand(time(0));

        pthread_create(&producer, 0, &produce, 0);
        pthread_create(&consumer, 0, &consume, 0);

        pthread_join(producer, 0);
        pthread_join(consumer, 0);

        return 0;

}

 

자 이제 컴파일 후 결과를 봅시다.

# gcc producer_consumer.c -lpthread
# ./a.out
produce:0
                consume:0
produce:1
                consume:1


...


produce:93
produce:94
produce:95
                consume:93
produce:96
                consume:94
produce:97
                consume:95
produce:98
                consume:96
produce:99
                consume:97
                consume:98
                consume:99

 

이상으로 간단한 조건변수 사용법을 알아보았습니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,