본문 바로가기
SP

[sp] Synchronization: Advanced

by 녕인뉸 2022. 5. 5.

**Semaphore review

Semaphore : s >= 0인 글로벌 정수 synchronization 변수, P와 V operation에 의해 조절

P(s) :

 - s != 0 -> s를 1 감소, 즉시 리턴

 - s == 0 -> s가 0이 아닐 때까지 스레드가 suspend => V operation에 의해 스레드가 wakeup

 - 스레드가 wakeup 후, P가 s를 감소시키고 caller에게 제어권을 넘겨줌

 

V(s) : 

 - s를 1 증가

 - P에서 s가 0이 아닐 때까지 기다리면서 block된 스레드가 있으면, 이 스레드 중 하나를 wakeup시켜주고, s를 감소시키면서 P를 완료

 

semaphore s는 무조건 0 이상

 

mutual exclusion을 통해 공유된 리소스를 보호하기 위해 semaphore를 사용하는 경우

 - basic idea : 1로 초기화된 semaphore인 mutex를 각 공유된 변수 ( 또는 공유된 변수와 관련된 집합)과 결합

  : P(mutex)와 V(mutex)가 있는 공유된 변수(s)로 각 access를 둘러쌈

 

mutex = 1
//mutex lock을 걸어줌
P(mutex)
cnt++
V(mutex)

 

1. Semaphore을 사용하여 공유된 리소스 (data structures)에 대한 access를 조정

 - basic idea : 스레드는 semaphore을 사용하여 몇몇 조건이 true가 되었음을 다른 스레드에게 알림

  - counting semaphore을 사용하여 리소스의 state를 유지하고 다른 스레드에게 알림

  - mutex를 사용하여 리소스에 대한 접근을 보호

 - examples : The producer-consumer problem, The Readers-Writes Problem

 

2. Producer-Consumer problem

producer thread : 생산, 버퍼를 채움, 만약 버퍼가 가득 차면 더이상 생산을 하지 않고 중단

consumer thread : 소비, 버퍼가 empty이면 consume 할 게 없으므로 중단

 

(1) Producer는 slot이 빌 때까지 wait, 버퍼에 item을 넣어주고 consumer에게 알림

(2) Consumer는 item을 기다리고, 버퍼로 부터 item을 remove, producer에게 알림

 

(3) ex

 - 멀티미디어 processing : 프로듀서는 MPEG 비디오 프레임을 생산하고 consumer에게 제공

 - 이벤트를 기반으로 한 graphical user interfaces : 프로듀서는 마우스의 클릭, 이동, 키보드 힛을 감지하고 버퍼에서 일치하는 이벤트에 넣음

 : consumer는 버퍼로 부터 이벤트를 찾고, 디스플레이에 paint

 

(4) Producer-Consumer on an n-element Buffer

 - mutex와 2개의 counting semaphore 필요

 : mutex - 버퍼에 대한 접근을 mutually exclusive하게 강요

 : slots - 버퍼의 가능한 slot을 셈, 즉 버퍼에서 남아있는 자리를 count, Producer의 입장에서 몇 개를 insert 할 수 있는지

 : items - 버퍼에서 가능한 item을 셈, 버퍼에 몇 개의 item이 들어와 있는지, Consumer 입장에서 몇 개를 소비할 수 있는 지

 

 - shared buffer package(sbuf)를 통해 구현

Declarations

#include "csapp.h”

typedef struct {
    int *buf; /* Buffer array */
    int n; /* Maximum number of slots */
    int front; /* buf[(front+1)%n] is first item */
    int rear; /* buf[rear%n] is last item */
    
    //binary semaphore
    sem_t mutex; /* Protects accesses to buf */
    
    //counting semaphore
    sem_t slots; /* Counts available slots */
    sem_t items; /* Counts available items */
} sbuf_t;

void sbuf_init(sbuf_t *sp, int n);
void sbuf_deinit(sbuf_t *sp);
void sbuf_insert(sbuf_t *sp, int item);
int sbuf_remove(sbuf_t *sp);

 

공유된 버퍼를 intializing 및 deinitializing

/* Create an empty, bounded, shared FIFO buffer with n slots */
void sbuf_init(sbuf_t *sp, int n)
{
    sp->buf = Calloc(n, sizeof(int));
    sp->n = n; /* Buffer holds max of n items */
    sp->front = sp->rear = 0; /* Empty buffer iff front == rear */
    Sem_init(&sp->mutex, 0, 1); /* Binary semaphore for locking */
    Sem_init(&sp->slots, 0, n); /* Initially, buf has n empty slots */
    Sem_init(&sp->items, 0, 0); /* Initially, buf has 0 items */
}

/* Clean up buffer sp */
void sbuf_deinit(sbuf_t *sp)
{
	Free(sp->buf);
}

초기 버퍼는 빈 버퍼이므로 front와 rear는 맨 처음을 같이 가르키고 있음

mutex는 처음에 1로 초기화

slots는 비어있는 버퍼의 자리를 의미하므로 n으로 초기화

item은 버퍼에 들어가 있는 item을 의미하므로 0으로 초기화

 

구현

공유된 버퍼에 아이템 삽입

/* Insert item onto the rear of shared buffer sp */
void sbuf_insert(sbuf_t *sp, int item)
{
    P(&sp->slots); /* Wait for available slot */
    //slot = 0이면 wait, 0이 아니면 감소하고 다음 코드 실행
    
    //mutex lock을 걸어줌
    P(&sp->mutex); /* Lock the buffer */
    sp->buf[(++sp->rear)%(sp->n)] = item; /* Insert the item */
    V(&sp->mutex); /* Unlock the buffer */
    //
    
    //block된 스레드를 깨워줌, item 증가시킴
    V(&sp->items); /* Announce available item */
}

 

공유된 버퍼로 부터 아이템 제거

/* Remove and return the first item from buffer sp */
int sbuf_remove(sbuf_t *sp)
{
    int item;
    P(&sp->items); /* Wait for available item */
    //item이 0이면 suspend, 0이 아니면 decrement하고 다음 코드 실행
    
    //mutex lock
    P(&sp->mutex); /* Lock the buffer */
    item = sp->buf[(++sp->front)%(sp->n)]; /* Remove the item */
    V(&sp->mutex); /* Unlock the buffer */
    
    //slot을 증가하고 slot이 없어서 wait하고 있는 스레드가 있으면 wakeup하고 작업 수행
    V(&sp->slots); /* Announce available slot */
    return item;
}

 

(5) Readers-Writers Problem

 - mutual exclusion problem의 일반화

 - Problem statement

 : Reader 스레드는 오직 object를 read만 함

 : Writer 스레드는 object를 수정

 : Writer는 반드시 object에 대해 exclusive access를 가짐

 -> 내가 write를 하는 중이면 read x, write x -> 즉, 혼자만 write할 수 있음

 : 무한한 reader는 object에 접근할 수 있어서 누구나 read할 수 있음

 

하지만 누군가가 read하고 있을 때 write는 wait

 

(6) Variants of Readers-Writers

- 문제1 : favors readrs ( reader에게 우선순위를 줌)

 -> writer가 object를 사용하기 위해 허가된 permission을 가질 때까지 reader는 wait하지 않음

 -> waiting writer 후에 도착한 reader는 writer보다 우선 순위를 가짐

즉 R->W->R일 때 W는 Wait하지만 W 뒤에 온 R은 object를 read할 수 있음

 

=> writer가 계속 wait하므로 starvation (스레드가 무한정으로 wait하는 상태) 발생

 

- 문제2 : favors writers ( writers에게 우선순위를 줌)

 -> writer가 읽을 준비가 되면, 가능한 write을 빠르게 완료

 -> writer가 wait하고 있는 중이라도 writer 후에 도착한 reader는 반드시 wait해야 함

 

=> reader가 계속 wait하므로 starvation 발생

 

- Solution to First Readers-Writers problem

//Readers
int readcnt; /* Initially = 0 */
sem_t mutex, w; /* Initially = 1 */
void reader(void)
{
    while (1) {
    P(&mutex);
    readcnt++;
    if (readcnt == 1) /* First in */
    	P(&w);
    V(&mutex);
    
    /* Critical section */
    /* Reading happens */
    
    P(&mutex);
    readcnt--;
    if (readcnt == 0) /* Last out */
    	V(&w);
    V(&mutex);
    }
}
//Writers
void writer(void)
{
    while (1) {
    	P(&w);
    /* Critical section */
    /* Writing happens */
        V(&w);
    }
}

 

 

(7) Putting it all together: Pthreaded Concurrent Server

 

쓰레드를 미리 생성해서 안쓰는건 sleep, 필요하면 깨움

 

- Prethreaded Concurrent Server

sbuf_t sbuf; /* Shared buffer of connected descriptors */
int main(int argc, char **argv)
{
    int i, listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    pthread_t tid;
    
    listenfd = Open_listenfd(argv[1]);
    sbuf_init(&sbuf, SBUFSIZE); 
    
    for (i = 0; i < NTHREADS; i++) /* Create worker threads */
    	Pthread_create(&tid, NULL, thread, NULL); 
        
    while (1) {
        clientlen = sizeof(struct sockaddr_storage);
        connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
        sbuf_insert(&sbuf, connfd); /* Insert connfd in buffer */
    }
}

 

worker thread routine

void *thread(void *vargp)
{
    Pthread_detach(pthread_self()); //쓰레드 종료시 쓰레드 자원 정리
    while (1) {
        int connfd = sbuf_remove(&sbuf); /* Remove connfd from buf */ //connfd를 버퍼에서 꺼냄
        echo_cnt(connfd); /* Service client */
        Close(connfd);
    }
}

 

echo_cnt initialization routine

static int byte_cnt; /* Byte counter */
static sem_t mutex; /* and the mutex that protects it */

static void init_echo_cnt(void)
{
    Sem_init(&mutex, 0, 1);
    byte_cnt = 0;
}

 

worker thread service routine

void echo_cnt(int connfd)
{
    int n;
    char buf[MAXLINE];
    rio_t rio;
    static pthread_once_t once = PTHREAD_ONCE_INIT;
    
    Pthread_once(&once, init_echo_cnt); //쓰레드 초기화를 한 번만
    Rio_readinitb(&rio, connfd); 
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
        P(&mutex);
        byte_cnt += n; //mutex lock으로 잡아둠
        printf("thread %d received %d (%d total) bytes on fd %d\n",(int) pthread_self(), n, byte_cnt, connfd); 
        V(&mutex);
        Rio_writen(connfd, buf, n);
    }
}

 

'SP' 카테고리의 다른 글

[sp] Thread-Level Parallelism  (0) 2022.05.17
[sp] Synchronization: Advanced (2)  (0) 2022.05.17
[sp] Synchronization: Basics  (0) 2022.05.03
[sp] Concurrent Programming (2)  (0) 2022.04.15
[sp] Concurrent Programming(1)  (0) 2022.04.11