ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lock-based Concurrent Data Structure
    운영체제 2024. 4. 18. 14:37

    Cuncurrent Counters

    typedef struct __counter_t {
    	int value;
    } counter_t;
    
    void init(counter_t *c) {
    	c->value = 0;
    }
    
    void increment(counter_t *c) {
    	c->value++;
    }
    
    void decrement(counter_t *c) {
    	c->value--;
    }
    
    int get(counter_t *c) {
    	return c->value;
    }

    Traditional 방식

    락을 사용하지 않는 카운터: 레이스 컨디션에서 부정확함

     

    typedef sturct __counter_t {
    	int value;
        pthread_mutex_t lock;
    } counter_t;
    
    void init(counter_t *c) {
    	c->value = 0;
        pthread_mutex_init(&c->lock);
    }
    
    void increment(counter_t *c) {
    	pthread_mutex_lock(&c=>lock);
        c-<value++;
        pthread_mutex_unlock(&c->lock);
    }
    
    void decrement(counter_t *c) {
    	pthread_mutex_lock(&c=>lock);
        c-<value--;
        pthread_mutex_unlock(&c->lock);
    }

    락을 사용한 경우 -> 정확하긴 한데 성능에 문제가 있음

     

    => Approximate Counter (= Sloppy Counter)

    예시) 코어 개수가 4개 -> 로컬 카운터 4개와 글로벌 카운터 1개

     

    typedef struct __counter_t {
    	int gloval;						// 글로벌 변수 값
        pthread_mutex_t glock;			// 글로벌 락
        int local[NUMCPUS];				// 로컬 변수 값
        pthread_mutex_t llock[NUMCPUS];	// 로컬 락
        int threshold;
    } counter_t;
    
    void init(counter_t *c, int threshold) {
    	c->threshold = threshold;
        c->global = 0;
        int i;
        
        int i;
        for( i = 0; i < NUMCPUS; i++ ) {
        	c->local[i] = 0;
            pthread_mutex_init(&c->llock[i], NULL);
        }
    }
    
    void update(counter_t *c, int threadID, int amt) {
    	int cpu = threadID % NUMCPUS;
        // 로컬 락
        pthread_mutex_lock(&c->llock[cpu]);
        
        c->local[cpu] += amt;
        
        if( c->local[cpu] >= threshold ) {
        	pthread_mutex_lock(&c-<glock);		// 글록벌 락
            c->global += c->local[cpu];
            pthread_mutex_unlock(&c->glock);	// 글로벌 언락
            c->local[cpu] = 0;
        }
        // 로컬 언락
        phtread_mutex_unlock(&c->glock);
    }
    
    // 리턴은 글로벌 값으로
    int get(counter_t *c) {
    	pthread_mutex_lock(&c->glock);
        int val = c->global;
        pthread_mutex_unlock(&c->glock);
        
        return val;
    }

    코어 개수만큼 로컬 락과 로컬 카운터를 두고, 로컬 카운터가 임계치(threshold)를 넘으면 글로벌 변수에 반영.

    100% 정확하진 않지만, 충돌이 적게 발생하여 훨씬 빠름.

     

    * 로컬 변수에 락을 두는 이유

    코어 수보다 스레드 수가 많은 경우, 로컬 변수에 대한 상호 배제가 이루어지지 않음


    Concurrent Linked Lists

    typedef struct __node_t {
    	int key;
        struct __node_t *next;
    } node_t;
    
    typedef struct __list_t {
    	node_t *head;
        pthread_mutex_t lock;
    } list_t;
    
    void List_Iint(list_t *L) {
    	pthread_mutex_init(&L->lock, NULL);
    }

     

    * 삽입의 비효율적인 경우

    int List_Insert(list_t *L, int key) {
    	// 락 
        pthread_mutex_lock(&L->lock);
        node_t *new = malloc(sizeof(node_t));
        
        if( new == NULL ) {
        	perror("malloc");
            pthread_mutex_unlock(&L->lock);
            return -1;	// fail
        }
        
        new->key = key;
        new->next = L->head;
        L->head = new;
        // 언락
        pthread_mutex_unlock(&L->lock);
        
        return 0;
    }

    이 경우, 임계 영역을 너무 넓게 잡아서 비효율적임 (그만큼 다른 스레드에서 접근 못하는 기간이 길어짐)

     

     

    * 효율 개선

    int List_Insert(list_t *L, int key) {
        node_t *new = malloc(sizeof(node_t));
        
        if( new == NULL ) {
        	perror("malloc");
            pthread_mutex_unlock(&L->lock);
            return -1;	// fail
        }
        
        new->key = key;
        // 락 
        pthread_mutex_lock(&L->lock);
        new->next = L->head;
        L->head = new;
        // 언락
        pthread_mutex_unlock(&L->lock);
        
        return 0;
    }

    임계영역은 리스트에 접근하는 경우만 해줘야 함

     

     

    * 디버깅이 힘든 Lookup

    int List_Lookup(list_t *L, int key) {
    	pthread_mutex_lock(&L->lock);
        node_t *curr = L->head;
        
        while( curr ) {
        	if( curr->key == key ) {
            	pthread_mutex_unlock(&L->lock);
                return 0;
            }
        }
        pthread_mutex_unlock(&L->lock);
        
        return -1;
    }

    리턴 문은 가능하다면 하나만 두는 것이 디버깅할 때 편함

     

    * 개선형 Lookup

    int List_Lookup(list_t *L, int key) {
    	int rv = -1;
    	pthread_mutex_lock(&L->lock);
        node_t *curr = L->head;
        
        while( curr ) {
        	if( curr->key == key ) {
            	rv = 0;
                break;
            }
        }
        pthread_mutex_unlock(&L->lock);
        
        return rv;
    }

    Cuncurrent Queues

    typedef struct __node_t {
    	int value;
        struct __node_t *next;
    } node_t;
    
    typedef struct __queue_t {
    	node_t *head;
        node_t *tail;
        pthread_mutex_t headLock;
        pthread_mutex_t tailLock;
    }
    
    void Queue_Init(queue_t *q) {
    	node_t *tmp = malloc(sizeof(node_t));
        tmp->next = NULL;
        q->head = q->tail = tmp;
        pthread_mutex_init(&q->headLock, NULL);
        pthread_mutex)init(&q->tailLock, NULL);
    }
    
    void Queue_Enqueue(queue_t *q, int *value) {
    	node_t *tmp = malloc(sizeof(node_t));
        assert( tmp != NULL );
        
        tmp->value = value;
        tmp->next = NULL;
        
        pthread_mutex_lock(&q->tailLock);
        q->tail->next = tmp;
        q->tail = temp;
        pthread_mutex_unlock(&q->lock);
    }
    
    int Queue_Dequeue(queue_t *q, int *value) {
    	pthread_mutex_lock(&q->headLock);
        node_t *tmp = q->head;
        node_t *newHead = tmp->next;
        
        if( newHead == NULL ) {
        	pthread_mutex_unlock(&q->headLock);
            return -1;
        }
        
        *value = newHead->value;
        q->head = newHead;
        pthread_mutex_unlock(&q->headLock);
        free(tmp);
        return 0;
    }

    enqueue는 테일에서, dequeue는 헤드에서 하니 같은 락으로 하기 보다는 분리해서 효율성을 높일 수 있음


    Cuncurrent Hash Table

    #define BUCKETS (101)
    
    typedef struct __hast_t {
    	list_t lists[BUCKETS];
    } hash_t;
    
    void Hash_Init(hash_t *H) {
    	int i;
        for( i = 0; i < BUCKETS; i++ ) {
        	List_Init(&H->lists[i]);
        }
    }
    
    int Hash_Insert(hash_t *H, int key) {
    	int bucket = key % BUCKETS;
        return List_Insert(&H->lists[bucket]);
    }
    
    int Hash_Lookup(hash_t *H, int key) {
    	int bucket = key % BUCKETS;
        return List_Insert(&H->lists[bucket], key);
    }

    해시는 사실상 멀티플 리스트, Cuncurrent List로 구현 가능

    한개의 락으로 관리하는 것보다 성능이 더 좋음 (fine-grained lock)

Designed by Tistory.