Interview

[OS] 프로세스 vs 스레드, 뮤텍스와 세마포어

전두선 2021. 7. 21. 13:07

[OS] 프로세스 vs 스레드

프로세스(Process)는 메모리 상에서 실행중인 프로그램(Program)을 의미한다. 프로세스마다 별도의 주소공간(code, data, heap, stack)을 독립적으로 할당 받으며, 최소 하나의 스레드를 보유하고 있다.

  • 두개 이상의 CPU가 협력적으로 하나 이상의 작업(Task)을 동시에 처리하는 것(병렬 처리)을 멀티 프로세스(Multi Process)라 부른다.
  • 독립적으로 실행되며 각각 고유의 메모리 할당을 받는 멀티 프로세스는 하나의 프로세스가 죽더라도 다른 프로세스에 영향을 주지 않기때문에 안정성이 높다는 장점이 있지만, 멀티 스레드보다 많은 메모리 공간과 Context Switching으로 성능저하가 발생한다.
- Code: 소스코드
- Data : 전역(global) 변수, 정적(static) 변수
- Heap: 동적할당, FIFO 구조, Runtime에 크기 결정
- Stack: 함수 호출과 관계되는 지역(local)/매개변수 저장, LIFO 구조, Compiletime에 크기 결정

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h> // for system()
#include <unistd.h> // for execl(), fork()
#include <wait.h>    // for wait()
 
int main(int argc, char *argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if(pid < 0) {            /* error occurred */
        fprintf(stderr,"Fork Failed");
        exit(-1);
    } else if (pid== 0) {    /* child process */
        //execl("/bin/ls", "ls", NULL);
        //execl("/bin/ps", "ps", "aux", NULL);
        system("/bin/ps aux");
        printf("Child here!\n");
        //for(;;);
    } else {          /* parent process */
        wait(NULL); 
        printf("Child Complete!\n");
        exit(0);
    }
}
cs

 


스레드(Thread)는 이 프로세스 내부에 실행되는 작업의 흐름 단위를 뜻한다. 스레드는 주소공간에서 stack만 따로 할당받고 나머지 영역은 스레드끼리 서로 공유하는 특징을 갖고있다.

  • 하나의 프로세스에 여러 스레드로 자원을 공유하며 작업을 나누어 수행하는 것을 멀티 스레드(Multi Thread)라 부른다. (CPU가 스레드를 하나씩 담당하는 방법)
  • 프로세스내의 메모리 공간을 공유하는 멀티 스레드는 멀티 프로세스보다 적은 메모리 공간을 차지하고 Context Switching이 빠른 장점이 있지만, 공유 자원으로 인한 동기화 문제와 하나의 스레드 장애로 전체 스레드가 종료 되는 단점(안정성이 낮음)이 있다. -> 이 동기화 문제를 해결하기 위해서 뮤텍스, 세마포어와 같은 솔루션을 사용해야함.
  • 멀티 스레드를 사용하면 프로세스를 생성하여 자원을 할당하는 시스템콜이 감소함으로써 시스템 자원의 효율적 관리가 가능하다. 또한 독립 구조인 프로세스 간의 통신(IPC)보다 공유 구조인 스레드 간의 통신 비용(처리 비용)이 적으므로 작업들 간 통신의 부담이 줄어든다. (IPC vs 스레드 간의 통신)
  • 스레드를 활용하면 시스템 자원의 효율성이 증가하지만, 스레드 간의 자원 공유는 전역 변수를 이용하므로 동기화 문제에 큰 주의가 필요하다.
문맥 교환(Context Switching)이란?

하나의 프로세스가 CPU를 사용 중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(Context)를 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말한다. 한 프로세스의 Context는 그 프로세스의 PCB(Process Control Block)에 기록되어 있다.

* Context Switching때 해당 CPU는 아무런 일을 하지 못하므로 Context Switching의 요청이 많이지면 오버헤드가 발생하여 효율이 떨어진다.
* 독립적으로 실행되며 각각의 메모리 공간을 갖는 프로세스 간의 Context-Switching 시에는 많은 자원 손실이 발생한다. 그러나 스레드 간의 Context-Switching에서는 메모리를 공유하고 있는 만큼 부담을 덜 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <pthread.h>
 
int thread_args[3= { 012 };  /* 쓰레드가 사용할 인자 */
//-------------------------------------------------------------
/* 쓰레드로 수행할 함수 */
void* Thread( void *arg )
{
    int i;
    for ( i=0; i<3; i++ )
        printf"thread %d: %dth iteration\n"*(int*)arg, i );
    pthread_exit(0);  /* 쓰레드 종료 함수 */
}
//-------------------------------------------------------------
int main( void )
{
    int i, clock_get;
    pthread_t threads[3]; /* 쓰레드 아이디를 위한 변수 */
    
    for ( i=0; i<3; i++ )  /* 쓰레드 생성 */
        // TODO: 스레드 생성하기
        pthread_create( &threads[i],                /* 쓰레드ID */
                        NULL,                       /* 쓰레드 속성 */
                        ( void* (*)(void*) )Thread, /* 쓰레드 시작 함수 */
                        &thread_args[i] );          /* 생성된 쓰레드에 전달 인자 */
    
    pthread_exit(0); /*메인 쓰레드 종료 */
}
cs

 

멀티프로세스와 멀티스레드는 둘다 여러 흐름이 동시에 진행된다는 공통점이 있지만, 각각의 장단점이 있으므로 사용자 환경에 따라 적합한 방식을 사용해야 한다.


세마포어(Semaphore) vs 뮤텍스(Mutex)

: 공유된 자원에 여러 프로세스 혹은 스레드가 동시에 접근하면 문제가 발생할 수 있다. 따라서 공유된 자원은 한 번에 하나의 프로세스만 접근할 수 있도록 제한을 줘야한다. 대표적인 방법으로는 세마포어와 뮤텍스가 있다.

 

- 세마포어(Semaphore): 세마포어는 유지할 수 있는 값의 범위에 따라 이진 세마포어(binary semaphore)카운팅 세마포어(counting semaphore)로 구분된다.

  • 이진 세마포어는 세마포어의 초기 값이 0또는 1만 가질 수 있는 세마포어이다. (뮤텍스와 비슷한 특성)
  • 카운팅 세마포어는 초기 값이 0 이상의 수를 가질 수 있다.
  • 세마포어는 공유 자원에 세마포어의 수만큼의 프로세스(또는 스레드)가 접근할 수 있음. 
  • 현재 수행중인 프로세스가 아닌 다른 프로세스(또는 스레드)가 세마포어를 해제할 수 있음.
  • 세마포어는 큐에 프로세스(또는 스레드)가 여러 개 있을 때 강성 세마포어약성 세마포어에 따라서 어떤 프로세스를 깨울지가 결정된다. 큐에서 선입선출(FIFO)로 깨운다면 강성 세마포어이며, 순서를 특별히 명시하지 않는다면 약성 세마포어이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#incude <stdio.h>
#include <semaphore.h>
#include <pthread.h>
 
sem_t        sem;  /*TODO: semaphore variable - sem */
pthread_t   task1, task2;
pthread_attr_t attr;
struct sched_param param;
//----------------------------------------
void get_thread( void ) {
    int i = 0;
    int val;
  
    while(1) {
        i++;
        sem_wait( &sem ); /* TODO: obtains semaphore, reduce it by 1 */
        sem_getvalue( &sem, &val ); /* obtains the semaphore value */
        printf"WAIT!\tsem count = %d\n" ,val );
        if ( i > 5 ) break;
    }
}
//----------------------------------------
void put_thread(void) {
    int i = 0;
    int val;
 
    while(1) {
        i++;
        sem_post( &sem ); /* TODO: semaphore post */
        sleep(1);
        sem_getvalue( &sem, &val ); /* obtains the semaphore value */
        printf"POST!\tsem count = %d\n", val );
        if ( i > 5 ) break;
    }
}
//----------------------------------------
int main( void ) {
    int i = 0, j, val;
 
    sem_init( &sem, 03 ); /* TODO: initialize unnamed semaphore */
    sem_getvalue(&sem, &val);
    printf"INIT!\tsem count = %d\n", val );
 
    pthread_attr_init(&attr);
    param.sched_priority = 20;
    pthread_attr_setschedparam(&attr, &param);
    pthread_attr_setschedpolicy( &attr, SCHED_FIFO );
 
    pthread_create( &task1, &attr, (void*(*)(void*))get_thread, NULL );
    pthread_create( &task2, &attr, (void*(*)(void*))put_thread, NULL );
    
    pthread_exit(0);
}
 
cs

 

- 뮤텍스(Mutex) 뮤텍스는 이진 세마포어와 같이 0과 1의 초기 값을 갖는다. 프로세스 또는 스레드가 임계영역에 들어갈 때 lock을 걸어 다른 프로세스(또는 스레드)가 접근하지 못하게하고, 임계영역에서 나와 unlock을 진행한다.

  • 뮤텍스는 오직 1개의 프로세스(또는 스레드)만 접근할 수 있다.
  • 뮤텍스는 lock을 획득한 프로세스가 반드시 해당 lock을 unlock 해야한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <semaphore.h>
#include <pthread.h>
#include <sys/types.h>
 
/* TODO: 뮤텍스 변수 선언 mutex, 선언과 동시에 초기화 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
int val;
int arg1 = 0, arg2 = 1;
//-------------------------------------------
void *Thread( void* arg ) 
{
    int i, j;
    
    for( i = 0; ; i++ ) {
        /* TODO: mutex 잠그기 */
        pthread_mutex_lock( &mutex );
        val = *(int*)arg;
        printf"thread %d: %dth iteration: val = %d\n"*(int*)arg, i, val);
        /* TODO: mutex 풀기 */
        pthread_mutex_unlock( &mutex );
        //for ( j=0; j<1000000; j++ );
        sleep(1);
    }
}
//-------------------------------------------
int main( void ) {
    pthread_t  thread1, thread2;
    pthread_attr_t attr;
    
    struct sched_param param;
    int policy;
    
    pthread_getschedparam( pthread_self(), &policy, &param );
    param.sched_priority = 1;
    policy = SCHED_RR;
    if ( pthread_setschedparam( pthread_self(), policy, &param ) != 0 ) {
        printf("main\n");
        while(1);
    }
    pthread_attr_init( &attr );
    pthread_attr_setschedpolicy( &attr, SCHED_RR );
    
    pthread_create( &thread1, &attr, (void*(*)(void*))Thread, &arg1 );
    pthread_create( &thread2, &attr, (void*(*)(void*))Thread, &arg2 );
    
    pthread_exit(0);
    return 0;
 
}
 
cs