4-8 동기화 (학습)

do·2022년 5월 15일
0

API

목록 보기
34/42
  • Race condition
    • 여러 프로세스가 공유 데이터를 동시에 접근할 때 실행 순서에 따라 실행 결과가 달라지는 상황
  • Critical section
    • 하나의 프로세스만 접근할 수 있는 영역
    • 경쟁 상태가 발생할 수 있는 코드 영역
    • 공유되는 자원에서 문제가 발생하지 않게 독점을 보장해줘야 하는 영역
    • 하나의 프로세스가 크리티컬 섹션에서 실행 중이라면, 다른 프로세스는 해당 영역에 접근할 수 없어야 한다.
    • 크리티컬 섹션에서 실행 중인 프로세스가 없을 때 들어가려는 프로세스가 여러 개라면 현재 대기 중인 프로세스만이 진입 후보가 될 수 있고, 어느 것이 들어갈 것인지 결정해주어야 한다.
    • 한 프로세스가 크리티컬 섹션에 진입 요청 후 받아들여질 때 까지의 시간동안 다른 프로세스들이 크리티컬 섹션에 진입하는 횟수는 제한이 있어야 한다.
    • 동일 프로세스 내의 스레드 사이에서만 동기화 가능하다
  • Mutex lock
    • 화장실이 하나 밖에 없는 식당과 비슷하다
    • 공유자원(화장실), 스레드(화장실을 이용하는 손님들), 큐(대기 줄), 뮤텍스(화장실 키)
    • 공유된 자원의 데이터 혹은 critical section 등에 하나의 프로세스 혹은 스레드가 접근하는 것을 막아줌 (즉, 동기화 대상이 하나)
  • 세마포어
    • 화장실이 여러 개 있고, 화장실 입구에는 현재 빈 화장실의 갯수를 알려주는 전광판이 있는 식당과 비슷하다
    • 모든 칸의 화장실이 사용 중일 때 전광판의 숫자는 0이 되며, 손님들은 전광판 숫자가 1로 바뀔 때까지 대기해야한다.
    • 용무를 마친 손님은 화장실에서 나오면서 전광판의 숫자를 +1 해주고, 대기하던 손님은 이 숫자에서 -1 해준 뒤에 화장실을 이용할 수 있다.
    • 공유된 자원의 데이터 혹은 critical section 등에 여러 프로세스 혹은 스레드가 접근하는 것을 막아줌 (즉, 동기화 대상이 하나 이상)
    • 기명 세마포어와 무기명 세마포어 크게 두가지로 분류된다.
      • 기명 세마포어는 말 그대로 이름이 있는 세마포어로 세마포어의 사용 및 구분은 이름을 통해서 할 수 있다.
      • 하지만 무기명은 이름이 없어서 프로세스 간에 공유하는 방식이 다르다.
        • 무기명의 경우에는 세마포어 디스크립터를 공유영역에 할당함으로써 프로세스 및 스레드 간에 사용할 수 있다.
        • 기명 세마포어의 함수들과 동일한데, 초기화하고 종료하는 함수만 다르다.
  • 뮤텍스와 세마포어의 차이
    • 세마포어는 정수값을 가진 변수로, 이 정수값이 접근할 수 있는 최대 허용치를 의미하며, 이 값 만큼 동시에 사용자가 접근할 수 있다.
      • semWait 연산: 세마포어 값을 감소시키고, 값이 음수가 되면 semWait을 호출한 프로세스는 블록된다.
    • 뮤텍스는 초기값을 1과 0으로 가진 변수이다. 1이면 카운터에 화장실 키가 있다는 뜻이다.
    • 뮤텍스의 경우, 락을 설정한 (값을 0으로 설정한) 프로세스만이 락을 해제할 수 있다. 반면, 세마포어의 경우, 락을 설정한 프로세스와 해제하는 프로세스가 다를 수 있다.
    • 세마포어는 시스템 범위에 걸쳐있고 파일 시스템 상의 파일 형태로 존재한다. 반면 뮤텍스는 프로세스 범위를 가지며 프로세스가 종료될 때 자동으로 clean up 된다.
    • 가장 큰 차이점은 관리하는 동기화 대상의 갯수이다.
  • Readers-writer lock
    • 읽기 전용 작업에 대한 동시 액세스를 허용하는 반면, 쓰기 작업에는 단독 액세스가 필요하다.
    • 스레드 간에 공유되는 데이터가 있을 때, 항상 모든 스레드가 그 데이터를 읽고 쓰는 것이 아니다.
    • 어떤 스레드는 해당 데이터를 읽기만 하고, 어떤 스레드는 쓰기만 하는 구조로 이루어질 수 있다.
    • 그리고 소수의 쓰기 스레드가 상대적으로 적은 횟수로 쓰기를 시행하고, 다수의 읽기 스레드가 상대적으로 빈번하게 읽기를 수행하는 경우가 많다.
    • 이런 경우에도 일반적인 락을 구현하여 읽기/쓰기를 수행하는 동안에 항상 락을 설정하고 해제한다면, 데이터를 단순히 읽기만 하여 값이 변경되지 않는 상황에도 불필요하게 critical section을 만들게 되므로 성능상 손해라고 할 수 있다.
  • deadlock
    • 시스템 자원에 대한 요구가 뒤엉킨 상태
    • 둘 이상의 프로세스가, 다른 프로세스가 점유하고 있는 자원을 서로 기다릴 때 무한 대기에 빠지는 상황

mutex

1. pthread_mutex_init()

함수 원형. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr *attr)
기능. 뮤텍스 객체 초기화
헤더. <pthread.h>
매개변수1. phtread_mutex_t *mutex 초기화 시킬 mutex 객체
매개변수2. const pthread_mutex_attr *attr 뮤텍스의 특징
기본값 NULL(fast; 하나의 스레드가 하나의 잠금만을 얻는 일반적인 형태)
recursive(잠금을 얻은 스레드가 다시 잠금을 얻을 수 있다. 이 경우 잠금에 대한 카운트가 증가함), errorchecking(에러 체크용)
리턴값. 성공 0, 실패 -1 errno

2. pthread_mutex_lock()

함수 원형. int pthread_mutex_lock(pthread_mutex_t *mutex)
기능. critical section에 진입하기 위해 뮤텍스 잠금을 요청한다.
만약 뮤텍스의 최근 상태가 unlocked면 스레드는 잠금을 얻고 critical section에 진입하게 되고 리턴한다.
다른 스레드가 뮤텍스 잠금을 얻은 상태라면 잠금을 얻을 수 있을 때까지 기다린다.
헤더. <pthread.h>
매개변수. pthread_mutex_t *mutex lock 시킬 뮤텍스 객체
리턴값. 성공 0, 실패 -1 errno

3. pthread_mutex_unlock()

함수 원형. int pthread_mutex_unlock(pthread_mutex_t *mutex)
기능. 뮤텍스 잠금을 되돌려준다.
만약 뮤텍스 특성이 fast라면, 언제나 unlcoked 상태로 되돌려준다.
만약 뮤텍스 특성이 recursive라면, 잠겨있는 뮤텍스의 수를 감소시키고 이 수가 0이 된다면 뮤텍스 잠금을 되돌려준다.
헤더. <pthread.h>
매개변수. pthread_mutex_t *mutex unlock 시킬 뮤텍스 객체
리턴값. 성공 0, 실패 -1 errno

4. pthread_mutex_destroy()

함수 원형. int pthread_mutex_destroy(pthread_mutex_t *mutex)
기능. 뮤텍스 객체를 삭제하고 자원을 되돌려준다.
헤더. <pthread.h>
매개변수. pthread_mutex_t *mutex destroy 시킬 뮤텍스 객체
리턴값. 성공 0, 실패 -1 errno
lock된 상태에서 뮤텍스를 destroy하며 안됨 (모든 lock이 다 해제가 되었을 때 destroy 해야한다.)

rwlock

5. pthread_rwlock_init()

함수 원형. int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *attr)
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
기능. rwlock 초기화
헤더. <pthread.h>
매개변수1. pthread_lock_t *restrict rwlock rwlock
매개변수2. const pthread_rwlockattr_t *attr
리턴값. 성공 0, 실패 -1 errno

6. pthread_rwlock_destroy()

함수 원형. int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
기능. rwlock 제거
헤더. <pthread.h>
매개변수. pthread_rwlock_t *rwlock rwlock
리턴값. 성공 0, 실패 -1 errno

7. pthread_rwlock_rdlock()

함수 원형. int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
기능. wait for a lock on a read or write lock object for reading
헤더. <pthread.h>
매개변수. pthread_rwlock_t *rwlock rwlock
리턴값. 성공 0, 실패 -1 errno

8. pthread_rwlock_wrlock()

함수 원형. int pthread_rwlock_rwlock(pthread_rwlock_t *rwlock)
기능. wait for a lock on a read or write lock object for writing
헤더. <pthread.h>
매개변수. pthread_rwlock_t *rwlock rwlock
리턴값. 성공 0, 실패 -1 errno

9. pthread_rwlock_unlock()

함수 원형. int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
기능. unlock a read or write object
If one or more threads are waiting to lock the rwlock, pthread_rwlock_unlock() causes one or more of these threads to return from the rdlock() or wrlock() call with the read or write lock object obtained.
If there are multiple threads blocked on rwlock for both read locks and write locks, z/OS UNIX will give the read or write lock to the next waiting call whether it is a read or a write request even when there is a writer blocked waiting for the lock. If no threads are waiting for the rwlock, the rwlock unlocks with no current owner.
헤더. <pthread.h>
매개변수. pthread_rwlock_t *rwlock rwlock
리턴값. 성공 0, 실패 -1 errno

semaphore

10. sem_init()

함수 원형. int sem_init(sem_t *sem, int pshared, unsigned int value)
기능. initialize an unnamed semaphore
세마포어 객체를 value값으로 초기화
헤더. <semaphore.h>
매개변수1. sem_t *sem 세마포어 포인터
매개변수2. int pshared
pshared = 0: 현재 프로세스에서 사용(프로세스 안 스레드끼리 공유)
pshared != 0: 여러 프로세스 간에 공유
(현재 리눅스는 세마포어가 여러 프로세스 간에 공유되는 것을 지원하지 않음. 따라서 0을 쓰지 않는 경우 항상 ENOSYS 에러코드를 반환한다.)
매개변수3. unsigned int value 세마포어가 가지는 초기값. 세마포어를 몇으로 초기화할지 의미한다. (화장실 열쇠를 몇 개로 시작할지 의미)
리턴값. 성공 0, 실패 -1 errno

sem_t s;
sem_init(&s, 0, 1);

11. sem_post()

함수 원형. int sem_post(sem_t *sem)
기능. unlock a semaphore
세마포어 값을 1 증가.
이 함수는 절대로 블록되지 않으며, 비동기 시그널 핸들러에서도 안전하게 사용.
헤더. <semaphore.h>
매개변수. sem_t *sem 세마포어 객체
리턴값. 성공 0, 실패 -1 errno

12. sem_wait()

함수 원형. int sem_wait(sem_t *sem)
기능. lock a semaphore
세마포어 값을 1 감소,
세마포어가 0일 경우에는 1 이상이 될 때까지 스레드는 대기 상태에 있는다.
0이 아닐 경우에는 대기 상태에서 빠져나와 세마포어를 또 1 감소시킨다.
헤더. <semaphore.h>
매개변수. sem_t *sem 세마포어 객체
리턴값. 성공 0, 실패 -1 errno

13. sem_open()

함수 원형. sem_t *sem_open(const char *name, int oflag, /* mode_t mode, unsigned int value */)
기능. 새로운 기명 세마포어를 생성하거나 기준 파일을 열 때 사용한다.
기명 세마포어는sem_init()으로 생성된 무명 세마포어보다 느리다.
세마포어는 시스템이 살아있기만 하면 계속 존재한다.
헤더. <semaphore.h>
매개변수1. const char *name 생성 또는 접근하고자 하는 세마포어의 이름
매개변수2. int oflag 세마포어 생성 시의 플래그. 이 두 가지 조합으로 사용 가능(O_CREAT/O_EXCL)
매개변수3. mode_t mode 플래그를 O_CREAT로 설정하면 mode 인자를 받을 수 있음. <sys/stat.h>을 include하면 다음 상수들을 활용할 수 있음(S_IRWXR 그룹접근, S_IRWXO 타인접근, S_IRWXU 개인접근)
매개변수4. unsigned int value 세마포어 초기값으로 0보다 큰 정수여야 함.
unlock된 세마포어의 갯수를 의미함. 이 값은 SEM_VALUE_MAX를 초과할 수 없다.
리턴값1. 성공 sem_t 세마포어 포인터
리턴값2. 실패 SEM_FAILED + errno 에러

함수 원형. int sem_unlink(const char *name)
기능. 세마포어 제거
헤더. <semaphore.h>
매개변수. const char *name 지우고자 하는 세마포어의 이름
리턴값. 성공 0, 실패 -1 errno

15. sem_getvalue()

함수 원형. int sem_getvalue(sem_t *sem, int *sval)
기능. 세마포어 현재값 추출
헤더. <semaphore.h>
매개변수1. sem_t *sem 세마포어 객체
매개변수2. int *sval sval이 가리키는 위치에 세마포어 현재 값을 저장
리턴값. 성공 0, 실패 -1 errno

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void *read(void *data);
void *accumulate(void *data);
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char *argv[])
{
        pthread_t p1, p2;

        sem_init(&sem_one, 0, 0);
        sem_init(&sem_two, 0, 1);

        pthread_create(&p1, NULL, read, NULL);
        pthread_create(&p2, NULL, accumulate, NULL);

        pthread_join(p1, NULL);
        pthread_join(p2, NULL);

        sem_destroy(&sem_one);
        sem_destroy(&sem_two);

        return 0;
}

void *read(void *data)
{
        int i;
        for (i = 0; i < 5; i++) {
                fputs("Input num: " ,stdout);

                sem_wait(&sem_two);
                scanf("%d", &num);
                sem_post(&sem_one);
        }
        return NULL;
}

void *accumulate(void *data)
{
        int sum = 0;
        int i;
        for (i = 0; i < 5; i++) {
                sem_wait(&sem_one);
                sum += num;
                sem_post(&sem_two);
        }
        printf("Result: %d\n", sum);
        return NULL;
}

0개의 댓글