첫번 째 과제 수행(Alarm System Call)을 수행하기 위해 알아야할 Thread에 대해 알아보자.
프로그램이 메모리에 로딩되어 실행 중인 상태인 프로세스(Process)를 기반으로 하는 멀티태스킹은 여러 프로세스를 동시에 실행시킴으로써 CPU(Central Processing Unit)의 활용률과 시스템의 처리율을 높였다. 그 결과 다양한 멀티태스킹 어플리케이션이 등장하게 되었지만 다음과 같은 여러 문제점들이 제기되었다.
프로세스마다 독립적인 메모리 공간을 갖기 때문에 자식 프로세스에게 부모 프로세스와는 별도로 메모리를 할당할 공간에 부모 프로세스를 복사하게되어 프로세스 생성에 많은 시간이 소요된다. 운영체제도 새로 생성되는 프로세스를 위해 PCB(Process Control Block), 페이지 테이블 등 프로세스 관리를 위한 구조체를 생성하는 데 많은 시간이 걸린다.
위와 같이 현재 실행 중인 프로세스를 중단시키고 다른 프로세스를 실행시키는 컨텍스트 스위칭에 따른 시공간적 오버헤드가 크다.
프로세스들은 각자 독립된 공간을 가지고 있기 때문에 다른 프로세스의 메모리에 접근할 수 없다. 그래서 프로세스들은 공유 메모리(sharded memory), 신호(signal), 파이프(pipe), 파일(file), 소켓(socket), 메시지 큐(message queue), 세마포(semaphore), 메모리 맵 파일(memory mapped filed, MMF) 등 다양한 방법을 통해 커널 메모리나 커널에 의해 마련된 제 3의 메모리 공간을 이용하여 데이터를 주고 받았다.
하지만 위 기능들은 다음과 같은 이유로 사용에 있어 여러 어려움이 있다.
위와 같은 프로세스의 문제점들을 해결하고자 하는 필요성이 증가함에 따라 스레드 (thread)라는 새로운 실행 단위가 출현하게 되었다.
스레드는 프로세스처럼 생성부터 소멸까지 여러 상태를 거치는 Life cycle을 가지고 있다. 운영체제마다 조금씩 다르게 구현되어 있지만 일반적으로 준비(ready state), 실행(running state), 대기(bloked state), 종료(terminated state)의 4가지 상태를 가지며 이 정보는 TCB(Thread Control Block) 구조체에 저장된다.
sleep()
과 같은 시스템 호출로 인해 커널에 의해 중단된 경우스레드의 존재를 인식하고 다룰 수 있는 커널이나 스레드 라이브러리에 의해 이루어지는 작동은 생성(create), 조인(join), 양보(yield), 종료(termination) 등이 있다.
프로세스와 마찬가지로 스레드는 새로운 스레드를 생성할 수 있다. 그러나 부모 스레드가 종료한다고 자식 스레드도 종료되는 것은 아니기 때문에 프로세스의 부모자식 관계에 비하면 스레드의 부모자식 관계의 의미는 크지 않다.
한 스레드가 다른 스레드가 작업을 끝내고 종료할 때까지 기다리는 것이 조인이다. 아무 스레드나 다른 스레드를 조인할 수 있으나, 일반적으로 부모 스레드가 특정 작업을 위해 자식 스레드를 생성하여 일을 시키고 자식 스레드가 작업을 완료하기를 기다릴 때 주로 사용한다.
실행 중인 스레드가 스스로 CPU를 다른 스레드에게 양보하기 위해 실행을 중단하는 행위가 양보이다. 양보한 스레드는 커널에 의해 준비 상태(ready state)가 되어 준비 큐(queue)에 들어가고, 준비 큐에 있는 스레드 중 하나가 스케줄링되어 실행된다. 이 기능은 매우 비싼 자원인 CPU를 스스로 다른 스레드에게 양보함으로써 효율적으로 사용하기 위해 만들어졌다.
프로세스의 종료는 exit()
시스템 호출을 호출하여 해당 프로세스를 비롯한 모든 스레드가 종료되는 것을 의미한다. 이와 달리 스레드 종료는 pthread_exit()
과 같이 스레드만 종료시키는 함수를 호출하는 경우이다. 이 때, 부모 스레드의 종료와 자식 스레드의 종료는 무관하며, 프로세스에 속한 마지막 스레드가 종료될 때 프로세스가 종료된다. 스레드가 다른 스레드를 생성할 수는 있었지만 강제로 종료할 수 있는 방법은 기본적으로 제공되지 않고 약속된 신호를 통해 스스로 종료하도록 설계하는 작업이 필요하다.