ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 멀티 스레드 / 경쟁 상태, 공유자원, 임계영역, 상호배제
    운영체제 2024. 4. 10. 22:48

    스레드: 제어의 흐름

    프로세스: 제어의 흐름 + 수행을 하기 위한 자원

     

    멀티 스레드 프로세스: 제어의 흐름들 + 수행을 하기 위한 자원

    -> 자원의 공유가 발생, 경쟁. 컨커런시 제어 필요

     

    컴퓨터 자원

    - CPU(레지스터)

    - 어드레스 스페이스 (코드 데이터 스탭 힙)

    - 파일

     

    어떻게 이런 자원을 관리하느냐

    프로세스 모델

    - 배타적으로 자원을 사용

    - fork는 모든 자원을 다 새로 만듦

    스레드 모델

    - 필요한 자원들 중 몇몇만 공유, CPU나 스택은 따로 가짐

    - 공유: 코드, 데이터, 힙, 파일

     

    당연히 스레드가 더 빠르고, 공유하는 측면에서도 스레드가 좋음

    -> 속도가 유리

     

    프로세스는 한 프로세스가 죽어도 다른 프로세스에 영향을 안끼치지만,

    스레드는 같은 프로세스 내 한 스레드가 죽으면 다 죽음

    -> 스레드는 결함의 고립(isolation)에 불리

     

    스레드의 장점

    - 만들기 빠름 : 프로세스는 헤비 웨이트, 스레드는 라이트 웨이트

    - 패럴리즘 : 소팅으로 예시를 들면 멀티스레드의 경우 분할 정복하면 됨 (맵 리듀스)

    - 웨이팅 오버랩하기 쉬움 : 서버가 요청을 받으면 커넥션 만들고 스레드 만들어서 실행시키고 자기는 다른 요청 다시 기다리고..

    - 데이터 쉐어링 쉬움

     

    스케줄링의 대상으로서는 같은 걸로 여겨지지만(스케줄링 엔티티), 데이터 공유의 관점에서 다름

    스레드는 TCB를 가지고, 프로세스처럼 상태전이도 하고, 자신만의 priority를 가질 수 있음

     

    이러한 특징 상 스케줄링 정책 따라 스레드 중 뭐가 출력될 지가 알 수가 없고,

    만들고 나면 컨트롤이 안됨

     

    결과가 계속 다르고 정답도 아닌 결과가 나오기도 함(non-determistic) -> 타이밍 버그

     

    타이머 인터럽트는 비동기적인 사건(어느 시점이든지 발생할 수 있음)

    기계어 수준에서 스케줄링 되는 걸 보면 원자적으로 실행되지 않아 미싱이 발생하기도 함

     

    해결책 -> 원자성

    모든 걸 하거나, 안하거나. 

    크리티컬 섹션(경쟁 상태를 야기하는 프로그램 코드) 부분은 원자적으로 처리되어야 함

    #include<stdio.h>
    #include<pthread.h>
    
    static volatile int counter = 0;
    
    void* mythread() {
    	for(i = 0; i < 1e7; i++) {
        	counter = counter + 1;
        }
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
    	pthread_t p1, p2;
        pthread_create(&p1, NULL, mythread, NULL);
        pthread_create(&p2, NULL, mythread, NULL);
        
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);
        
        return 0;
    }


    1. 공유 자원

    2. 임계 영역 (critical section)

    3. 경쟁 상태 (race condition)

    4. 상호 배제 (mutual exclusion)

     

    위 코드에서 공유 자원은 전역변수 counter, 임계 영역은 mythread()의 counter = counter + 1이 될 것이고,

    이 코드 앞 뒤로 락, 언락을 해주면 상호 배제가 될 것이다. 두 스레드가 카운터에 동시에 접근하려는 상태가 경쟁상태이다.

     

     

    Concurrency의 두가지 이슈

    1. 한 순간에 임계영역에 들어오는 스레드는 하나여야 한다

    2. 동기화 : 한 스레드의 어느 부분은 항상 다른 스레드를 기다려야 한다

     

     

    스레드 API

    유저레벨 스레드: 2000년 초에 좀 쓰였고 요즘엔 안 씀

    커널레벨 스레드

    - 커널에서 스레드를 지원

    - pthread, 리눅스 스레드, 자바스레드..

     

    pthread

    - 생성

    - 대기

    - 뮤텍스락, 언락

    - 조건변수

     

    * 스레드를 만들 때 어떤 값을 만들어서 리턴해야 하는 경우

    => 동적할당해서 넘겨야 함

    리턴 값이 스택에 들어가면 스레드 끝나는 순간 날아가니까 주의

     

    컨커런시 메커니즘

    1. 락(lock)

    // example
    pthread_mutex_t lock;
    int rc = pthread_mutex_init(&lock, NULL);
    assert( rc == 0 );
    
    pthread_mutex_lock(&lock);
    x = x + 1;
    pthread_mutex_unlock(&lock);

    상호배제를 위함. 락과 언락의 사이는 원자성이 보장

     

    2. 동기화 (synchronization)

    // thread 1
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    pthread_mutex_lock(&lock);
    while(ready == 0)
    	pthread_cond_wait(&cond, &lock);
    pthread_mutex_unlock(&lock);
    // thread 2
    pthread_mutex_lock(&lock);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&lock);

    wait와 signal을 하려면 우선 락을 해야 함. 조건 변수를 쓰기 전 락은 필수

    스레드 둘 중 뭐가 먼저 실행될지는 몰라도, 스레드 2의 윗부분이 스레드 1의 밑부분보다 먼저 실행되는게 보장됨

    순서 관계가 보장되는 경우

     

    * 조건이 맞지 않으면 계속 웨이트..

    그런데 락 걸고 웨이트 들어갔는데 어떻게 락이 또 걸리냐?

    wait 들어가기 전에 락을 풀어주고, 깨어날때 다시 잠금

    => 데드락이 예방됨

Designed by Tistory.