dayne의 블로그

리눅스 fork와 thread의 차이점과 및 예제 본문

시스템 프로그래밍/리눅스

리눅스 fork와 thread의 차이점과 및 예제

dayne_ 2024. 10. 8. 20:57

목차

1. fork와 thread에 대한 설명

2. fork와 thread의 공통점과 차이점

3. fork와 thread 예제 코드

 

 


1. fork와 thread에 대한 설명

1.1 fork

Unix 환경에서 fork() 함수는, 해당 함수를 호출한 프로세스를 복사하는 기능을 수행합니다.


이때, 부모 프로세스와 자식 프로세스가 나뉘어 실행되는데, 원래 진행되던 프로세스는 부모 프로세스(parent), 복사된 프로세스는 자식 프로세스(child)라고 합니다.

 

fork()함수는 프로세스 id를 의미하는 pid를 반환하게 되는데, 부모 프로세스에서는 자식 pid가 반환되고, 자식 프로세스에서는 0이 반환됩니다. 만약 fork() 함수 실행이 실패하면 -1을 반환하게 됩니다.

 

fork() 함수는 COW(Copy On Write) 방식을 사용하기 때문에, 자식 프로세스와 부모 프로세스가 독립적으로 변수를 사용할 수 있습니다. 자식 프로세스의 변수를 변경하였을 때, 부모 프로세스의 변수는 변하지 않고 그대로 유지됨을 의미합니다.

 

1.2 thread

쓰레드란, '프로세스 내에서 실제로 작업을 수행하는 주체'를 의미합니다.

 

모든 프로세스는 한 개 이상의 쓰레드가 존재해 작업을 수행하게 되고, 2개 이상의 쓰레드를 가지는 프로세스를 '멀티 쓰레드 프로세스'라고 합니다. 멀티 쓰레드를 사용하여, 동일한 프로세스 내에서 여러 실행 흐름을 생성하고, 프로그램의 여러 부분을 동시에 수행시킬 수 있게 됩니다

 

프로세스는 독립된 각각의 프로세스들이 독립된 영역을 가지고 있는 반면, 쓰레드는 하나의 프로세스 내에서 여러 개 존재하므로, 해당 프로세스에 있는 메모리 공간을 서로 공유하게 됩니다.

 

멀티 쓰레드가 사용되는 예시는 흔히 사용하는 웹 브라우저에서도 확인할 수 있습니다. 이미지와 텍스트 등 화면에 홈페이지를 표시하는 쓰레드 1개가 존재한다면, 백엔드로부터 데이터를 읽어오는 역할을 수행하는 쓰레드가 따로 동작하고 있을 수도 있습니다. 이렇게 동시에 여러 기능들을 수행하기 위해서 멀티 쓰레드가 사용됩니다.

 

※ 쓰레드의 필요성

최근 프로그램들이 매우 복잡해지면서, 하나의 프로세스만 가지고 실행하기에 힘들어진 상황입니다.


이로 인해 "한 프로그램을 처리하기 위해 여러 프로세스를 사용하고 싶다"라는 요구사항이 생겼지만, OS는 안정성을 위해 각 프로세스마다 자신에게 할당된 메모리 내의 정보에만 접근할 수 있도록 제한을 두고 있었습니다. 따라서 하나의 어플리케이션을 위해 여러 개의 프로세스를 할당하여 사용할 경우, 많은 제약 사항이 생긴다는 한계가 있었습니다.


그래서, 프로세스 내에 메모리를 공유하면서 여러가지 일을 할 수 있게 하기 위해 "쓰레드"가 만들어진 것입니다.

 

 


2. fork와 thread의 공통점과 차이점

2.1 공통점

  • 병렬 실행 : fork와 thread는 둘 다 병렬 처리를 통해 여러 작업들을 동시에 수행하는 방식으로 동작합니다.

 

2.2 차이점

  • 독립성 (자원 관리)
    • fork는 새로운 자식 프로세스를 생성하고, 해당 자식 프로세스는 부모 프로세스와 독립된 메모리 공간을 가지며 별도의 주소 공간에서 실행됩니다. 즉, 두 프로세스가 메모리 공간을 공유하지 않습니다.
    • thread는 동일한 프로세스 내에서 실행되며, 쓰레드 간에 메모리 공간을 공유합니다. 여러 쓰레드가 하나의 프로세스 주소 공간을 공유하기 때문에, 한 쓰레드에서의 자원 변경이 다른 쓰레드의 실행에 영향을 주게 됩니다.
  • 비용과 성능
    • fork는 새로운 프로세스를 생성하기 위해 많은 메모리 공간과 시스템 자원이 필요하므로, 비용이 크게 소요됩니다.
    • thread는 여러 쓰레드가 같은 메모리 공간을 공유하므로, 상대적으로 적은 비용이 소요됩니다.

 

 


3. fork와 thread 예제 코드

3.1 fork 예제 코드

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    pid_t pid;
    
    pid = fork();
    
    if(pid == -1) {
    	printf("can't fork. error\n");
        exit(0);
    }
    
    if(pid == 0) {
    	printf("child pid : %d\n", getpid());
        for(int i = 0; i < 10; i++) {
        	printf("child : %d\n", i);
            sleep(1);
        }
        exit(0);
    }else {
    	printf("parent pid : %d\n", getpid());
        for(int j = 0; j < 10; j++) {
        	printf("parent : %d\n", j);
            sleep(1);
        }
        exit(0);
    }
    
    return 0;
}

 

위의 코드는 fork() 시스템콜의 사용 예시입니다.

 

우선 pid_t 타입의 pid 변수를 선언해줍니다.

pid_t는 sys/type.h 헤더 파일에 정의된 primative 타입의 자료형으로, 프로세스 번호(pid)를 저장하는 자료형입니다.

 

이후, fork() 시스템콜을 호출하고, 반환되는 값을 pid에 저장합니다.

fork() 시스템콜을 호출하게 되면, 부모 프로세스는 자식 프로세스의 pid 값을 반환받고, 자식 프로세스는 0을 반환받게 됩니다.

 

우선, pid 변수의 값이 -1인 경우에는 의도한 대로 동작하지 않은 상황이기 때문에, error라는 문구를 출력하고 프로그램을 종료합니다.

 

pid 변수의 값이 0인 경우에는, 자식 프로세스임을 의미합니다.

따라서 getpid()의 값을 통해 자식 프로세스의 pid 값을 출력해 주고, 자식 프로세스에서 0부터 9까지 출력해 줍니다.

 

pid 변수의 값이 0이 아닌 경우, 다시 말해서 fork() 시스템콜 호출의 반환값으로 0을 받지 않고 자식 pid 값을 받은 경우에는 부모 프로세스임을 의미합니다.

따라서 getpid()의 값을 통해 부모 프로세스의 pid 값을 출력해 주고, 부모 프로세스에서 0부터 9까지 출력해 줍니다.

 

출력 결과는, 자식 프로세스와 부모 프로세스가 번갈아가며 0부터 9까지 출력하는 것을 확인하실 수 있을 것입니다.

 

 

3.2 thread 예제 코드

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *p_function(void *data) {
    pid_t pid;
    pthread_t tid;
    
    pid = getpid();
    tid = pthread_self();
    
    char *thread_name = (char *)data;
    int i = 0;
    
    while(i++ < 3) {
    	printf("thread name : %s, tid = %x, pid = %u\n", thread_name, (unsigned int) tid, (unsigned int) pid);
        sleep(1);
    }
}    
int main(void) {
	pthread_t pthread[2];
    int thread_id;
    int status;
    char p1[] = "thread_1";
    char p2[] = "thread_2";
    char p3[] = "thread_3";
    
    thread_id = pthread_create(&pthread[0], NULL, p_function, (void *)p1);
    if(thread_id != 0) {
    	perror("pthread0 create error\n");
        exit(EXIT_FAILURE);
    }
    
    thread_id = pthread_create(&pthread[1], NULL, p_function, (void *)p2);
    if(thread_id != 0) {
    	perror("pthread1 create error\n");
        exit(EXIT_FAILURE);
    }
    
    p_function((void *)p3);
    
    // 메인 쓰레드에서 쓰레드가 종료될 때까지 기다림
    pthread_join(pthread[0], (void **)&status);
    pthread_join(pthread[1], (void **)&status);
    
    printf("end..\n");
    
    return 0;
}

 

위의 코드는 pthread 사용 예시입니다.

 

우선 위의 예제에 사용된 thread 관련 함수들을 정리하고, 예제 코드에 대해 설명드리겠습니다.

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
    • 함수 설명
      • 함수를 호출하면, 새로운 쓰레드가 생성됩니다.
      • 성공 시 0을 반환하고 thread에 쓰레드의 식별자가 저장됩니다. 실패 시,  오류 번호가 반환되며 쓰레드는 생성되지 않습니다.
    • 매개변수 설명 
      • pthread_t *thread : 쓰레드 생성 성공 시, 생성된 쓰레드를 식별하기 위해 사용되는 쓰레드 식별자가 저장됨
      • const pthread_attr_t *attr : 쓰레드와 관련된 속성을 지정해 주는데 사용하고, 일반적으로 NULL 값을 넣어 기본 설정으로 사용
      • void *(*start_routine)(void *) : 새로 생성된 쓰레드가 실행할 함수
      • void *arg : 쓰레드가 실행할 함수에 들어갈 인자
      • (void를 사용하는 이유 : 넘겨주는 자료형이 정해진 게 아니기 때문. 상황에 맞게 함수 내에서 원래 자료형으로 형변환 후 사용 가능)
  • int pthread_join(pthread_t thread, void **retval);
    • 함수 설명
      • 특정 쓰레드가 종료될 때까지 대기하며, 쓰레드가 이미 종료된 경우에는 즉시 반환됩니다. 쓰레드가 완료된 후 반환하는 값을 얻을 수 있습니다.
      • 성공 시 0을 반환하고, 실패 시 오류 번호를 반환합니다.
    • 매개변수 설명
      • pthread_t thread : 종료를 기다릴 쓰레드의 식별자를 의미, pthread_create() 메서드로 생성된 쓰레드의 식별자를 전달
      • void **retval : 종료된 쓰레드가 반환한 값을 저장할 포인터의 주소로, 쓰레드 함수(start_routine)가 반환한 값을 받을 수 있습니다.

 

다음으로 예제 코드에 대해 설명드리겠습니다.

 

<p_function(void *data) 설명>

우선 각각의 thread에서 동작할 p_function(void *data) 메서드를 정의해 줍니다.

 

프로세스 아이디와 thread의 아이디를 저장하기 위해 pid_t 타입의 pid 변수와, pthread_t 타입 변수의 tid 변수를 선언해 줍니다.

 

이후, 현재 프로세스 아이디와 쓰레드 아이디를 각각 pid, tid 변수에 저장합니다.

여기서 pthread_self()는 현재 동작 중인 쓰레드의 식별자를 반환합니다.

 

함수의 매개변수로 받은 문자열을 thread_name 변수에 저장하고, int 타입 변수 i를 0으로 초기화합니다.

 

그리고 반복문을 통해 '쓰레드 이름'과 '현재 동작 중인 쓰레드의 식별자', '프로세스 번호' 를 출력하는 동작을 3번 반복합니다.

 

<main함수 설명>

pthread_t 타입의 pthread[2] 배열과, int 타입 thread_id, status 변수들을 선언합니다.

그리고 각각의 쓰레드를 구분하기 위해 p1, p2, p3에 지정할 쓰레드 이름을 저장합니다.

 

pthread_cerate(&pthread[0], NULL, p_function, (void *)p1)을 호출하고, 반환 값을 thread_id에 저장합니다.

pthread_create() 메서드에 의해 쓰레드가 정상적으로 생성되었을 경우, 생성된 쓰레드는 '인자로 p1을 갖는 p_function() 메서드'를 수행하고, 생성된 쓰레드의 식별자는 pthread[0]에 저장하게 됩니다. 그리고 반환 값으로는 0을 반환하게 되므로, thread_id의 값은 0이 될 것입니다.

만약 thread_id의 값이 0이 아닌 경우, 쓰레드가 정상적으로 생성되지 않은 상황이므로 에러 문구를 출력하고 종료합니다.

 

pthread[1]에 대해서도, pthread[0]에 새로 생성된 쓰레드를 저장했던 과정과 에러 처리 로직을 동일하게 작성해 줍니다.

 

메인 쓰레드에서 p3를 인자로 가지는 p_function() 메서드를 호출하여 수행합니다.

 

이후, pthread_join(pthread[0], (void **)&status);와 pthread_join(pthread[1], (void**) &status) 메서드를 호출함으로써, main 함수를 수행 중인 쓰레드가 특정 쓰레드(pthread[0], pthread[1])의 종료를 기다리게 됩니다. 

※pthread_detach() 메서드를 사용할 경우, pthread_join을 사용하지 않더라도 쓰레드를 기다리지 않고 종료합니다.

 

마지막으로 main함수를 수행하는 쓰레드가 끝나기 전에, 'end..'을 출력하고 프로그램이 종료됩니다.

 

 


참고

https://code-lab1.tistory.com/39

 

[운영체제] fork() 함수란? , fork() 함수 예제 , 부모 자식 프로세스

fork() 함수란?Unix 환경에서 fork() 함수는 함수를 호출한 프로세스를 복사하는 기능을 한다. 이때 부모 프로세스와 자식 프로세스가 나뉘어 실행되는데, 원래 진행되던 프로세스는 부모 프로세스(p

code-lab1.tistory.com

 

https://velog.io/@qlgks1/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC

 

리눅스 - 프로세스(Process)와 쓰레드(Thread) & linux ps, 작업(job) handling

리눅스의 프로세스(process) 는 컴퓨터 내에서 프로그램을 실행하는 주체가 된다. 프로세스의 기본적인 관리법과 job control에 대해 체크하고 automatic tasks 등 리눅스를 더 고차원적으로 활용할 수 있

velog.io

 

 

https://shyeon.tistory.com/45

 

[Linux] 리눅스 시스템 프로그래밍 thread(스레드) 생성과 종료

스레드란? 프로세스 내의 제어 흐름 일반적으로 우리가 작성하는 코드는 단일 스레드 단일 프로세스 다중 스레드 프로세스는 하나의 프로세스에 여러 컨트롤이 존재함 쉽게 말해 스레드란 우리

shyeon.tistory.com

 

https://thdev.net/176

 

프로세스 생성 함수 fork() 사용 예제

fork() 함수를 이해하는데 상당히 오랜시간이 걸렸습니다.위키피디아에 설명이 되어있고, 예제 코드도 존재합니다. http://en.wikipedia.org/wiki/Fork_(operating_system) 제가 만든 예제는 fork()를 이용하여 부

thdev.net

 

https://m.blog.naver.com/whtie5500/221692793640

 

[C] pthread란? pthread예제

오늘은 pthread입니다! 아마 운영체제시간에 배울텐데요~ 저는 운영체제수업시간에 배웠거든요! 흠 오늘은 ...

blog.naver.com

 

https://velog.io/@hokim/pthread-%ED%95%A8%EC%88%98-%EC%A0%95%EB%A6%AC

 

pthread 함수 정리

기본형 매개변수 정보 attr 정보1) 함수 호출pthread_create() 함수 호출 시 새 스레드가 생성되고, 생성된 스레드는 start_routine(arg)를 실행합니다.생성된 스레드는 다음 세가지 경우에 종료됩니다.

velog.io