프로세스(Process)와 쓰레드(Thread) - 이론편

JD_S·2022년 11월 7일
0

Java

목록 보기
12/21

프로세스

컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램. 종종 스케줄링의 대상이 되는 작업(task)이라는 용어와 거의 같은 의미로 쓰인다. 여러 개의 프로세스를 사용하는 것을 멀티프로세싱이라고 하며 같은 시간에 여러 개의 프로그램을 띄우는 시분할 방식을 멀티테스킹이라고 한다. 프로세스 관리는 운영 체제의 중요한 부분이다.

프로그램과 프로세스

프로그램 : 하드 디스크 등에 저장되어 있는 실행 코드.
프로세스 : 프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리 상에서 실행되는 작업 단위. 즉, 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말한다. 이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 쓰레드로 구성된다. (하나의 프로그램을 여러 번 구동하면 여러 개의 프로세스가 메모리 상에서 실행된다.)

프로세스의 상태

커널 내에는 준비 큐, 대기 큐, 실행 큐 등의 자료 구조가 있으며 커널은 이것들을 이용하여 프로세스의 상태를 관리한다. (1. 커널이 뭘까?)

  • 생성(Created): 프로세스가 생성되는 중이다.
  • 실행(Running): 프로세스가 CPU를 차지하여 명령어들이 실행되고 있다.
  • 준비(Ready): 프로세스가 CPU를 사용하고 있지는 않지만 언제든지 사용할 수 있는 상태로, CPU가 할당되기를 기다리고 있다. 일반적을 준비 상태의 프로세스 중 우선순위가 높은 프로세스가 CPU를 할당받는다. (2. 우선순위는 어떻게 정할까?)
  • 대기(Waiting): 보류(Block)라고 부르기도 한다. 프로세스가 입출력 완료, 시그널 수신 등 어떤 사건을 기다리고 있는 상태를 말한다. (3. 시그널 수신이 뭘까?)
  • 종료(Terminated): 프로세스의 실행이 종료되었다.

프로세스의 상태전이

하나의 프로그램이 실행되면 그 프로그램에 대응되는 프로세스가 생성되어 준비 리스트의 끝에 들어간다. 준비 리스트 상의 다른 프로세스들이 CPU를 할당받아 준비 리스트를 떠나면, 그 프로세스는 점차 준비 리스트의 앞으로 나가게 되고 언젠가 CPU를 사용할 수 있게 된다.

  • 디스패치(Dispatch)

    • 준비 리스트의 맨 앞에 있던 프로세스가 CPU를 점유하게 되는 것, 즉 준비 상태에서 실행 상태로 바뀌는 것을 디스패치라고 하며 다음과 같이 표시한다.

      dispatch(processname) : ready -> running

  • 보류(Block)

    • 실행 상태의 프로세스가 허가된 시간을 다 쓰기 전에 입출력 동작을 필요로 하는 경우 프로세스는 CPU를 스스로 반납하고 보류 상태로 넘어 간다.

      block(processname) : running -> blocked

  • 시간제한(Timeout)

    • 운영체제는 프로세스가 프로세서를 계속 독점해서 사용하지 못하게 하기 위해 Clock Interrupt를 두어서 프로세스가 일정 시간동안만 (시분할 시스템의 Time Slice)프로세스를 점유할 수 있게 한다. (4. Clock Interrupt와 Time Slice가 정확히 뭘까?)

      timeout(processname) : running -> ready

쓰레드

어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위이다. 일반적으로 한 프로그램은 하나의 쓰레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 쓰레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티쓰레드라고 한다.

프로세스와 쓰레드의 비교

멀티프로세스와 멀티쓰레드는 양쪽 모두 여러 흐름이 동시에 진행된다는 공통점을 가지고 있다. 하지만 멀티프로세스에서 각 프로세스는 독립적으로 실행되며 각각 별개의 메모리를 차지하고 있는 것과 달리 멀티쓰레드는 프로세스 내의 메모리를 공유해 사용할 수 있다. 또한 프로세스 간의 전환 속도보다 쓰레드 간의 전환 속도가 빠르다.

멀티쓰레드의 다른 장점은 CPU가 여러 개일 경우에 각각의 CPU가 쓰레드 하나씩을 담당하는 방법으로 속도를 높일 수 있다는 것이다. 이러한 시스템에서 여러 쓰레드가 실제 시간상으로 동시에 수행될 수 있기 때문이다.

멀티쓰레드의 단점에는 각각의 쓰레드 중 어떤 것이 먼저 실행될지 그 순서를 알 수 없다는 것이다. (5. 왜 알 수 없는 걸까?) 예를 들어, 두 쓰레드가 특정 공유 변수 i의 값을 1씩 증가시키는 명령을 실행할 때, 다음과 같은 방식으로 수행될 수 있다.

  • 공유되는 변수 i의 값을 레지스터에 저장
  • 레지스터의 값을 1씩 증가
  • 변수 i에 그 값을 저장

이때 두 쓰레드가 실행될 때 어떤 쓰레드가 먼저 실행될지는 보장되지 않으며, 만약 다음과 같은 순서로 실행된다면

최종 결과로 i는 2가 증가된다. 하지만 다음과 같이 실행된다면:

최종 결과로 i는 1이 증가되고, 이것은 원래 프로그램의 의도(각각의 쓰레드가 i를 1씩 증가하는 동작)와는 다를 수 있다. 이러한 문제는 쓰레드의 실행 조건에 따라 결과가 다르게 나오므로, 오류가 발생했을 때 원인을 찾기가 힘들다. 이러한 문제를 경쟁 조건이라고 하며, 문제를 막기 위해 세마포어와 같은 방법을 통해 공유 데이터에 접근하는 쓰레드의 개수를 한개 이하로 유지하는 방법을 사용할 수 있다.

쓰레드의 종류

쓰레드를 지원하는 주체에 따라 2가지로 나눌 수 있다.

사용자 레벨 쓰레드(User-Level Thread)

사용자 쓰레드는 커널 영역의 상위에서 지원되며 일반적으로 사용자 레벨의 라이브러리를 통해 구현되며, 라이브러리는 쓰레드의 생성 및 스케줄링 등에 관한 관리 기능을 제공한다. 동일한 메모리 영역에서 쓰레드가 생성 및 관리되므로 속도가 빠른 장점이 있는 반면, 여러 개의 사용자 쓰레드 중 하나의 쓰레드가 시스템 호출 등으로 중단되면 나머지 모든 쓰레드 역시 중단되는 단점이 있다. 이는 커널이 프로세스 내부의 쓰레드를 인식하지 못하며 해당 프로세스를 대기(Waiting) 상태로 전환시키기 때문이다.

커널 레벨 쓰레드

위의 기본 데이터 외에도 하나의 쓰레드에만 연관된 데이터가 필요한 경우가 있는데, 이런 데이터를 쓰레드 특정 데이터(Thread-Specific Data, TSD)라고 한다. 멀티 쓰레드 프로그래밍 환경에서 모든 쓰레드는 프로세스의 데이터를 공유하고 있지만, 특별한 경우에는 개별 쓰레드만의 자료 공간이 필요하다. 예를 들어 여러 개의 트랜잭션을 쓰레드로 처리할 경우, 각각의 트랜잭션 ID를 기억하고 있어야 하는데, 이때 TSD가 필요하다. TSD는 여러 쓰레드 라이브러리들이 지원하는 기능 중 하나이다.

프로세스 관리의 변화

멀티 쓰레드 환경이 확산됨에 따라 전통적인 프로세스 관리 방식에도 변화가 필요해졌다. 예를 들어 fork 또는 exec와 같은 시스템 호출시에 어떻게 처리할 것인가 하는 문제가 대두된 것이다. (6. fork()와 exec()의 역할은 뭐고 차이는 무엇인가?)

  • fork 문제 : 어떤 프로세스 내의 쓰레드가 fork를 호출하면 모든 쓰레드를 가진 프로세스를 생성할 것인지, 아니면 fork를 요청한 쓰레드만 가진 프로세스를 생성할 것인지 하는 문제이다. 유닉스에서는 각각 2가지 버전의 fork를 지원한다.
  • exec 문제 : fork를 통해 모든 쓰레드를 복제하고 난 후, exec를 수행한다면 모든 쓰레드들이 초기화된다. 그렇다면 교체될 쓰레드를 복제하는 작업은 필요가 없기 때문에 애초에 fork를 요청한 쓰레드만을 복재했어야 한다. 한편, fork를 한 후에 exec를 수행하지 않는다면 모든 쓰레드를 복제할 필요가 있는 경우도 있다.

Reference

profile
Whatever does not destroy me makes me stronger.

0개의 댓글