스레드 (컴퓨팅)

전자 게시판의 종류 두 개의 스레드를 실행하고 있는 하나의 프로세스. **스레드**(thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.
# 프로세스와 스레드의 비교
멀티프로세스와 멀티스레드는 양쪽 모두 여러 흐름이 동시에 진행된다는 공통점을 가지고 있다. 하지만 멀티프로세스에서 각 프로세스는 독립적으로 실행되며 각각 별개의 메모리를 차지하고 있는 것과 달리 멀티스레드는 프로세스 내의 메모리를 공유해 사용할 수 있다. 또한 프로세스 간의 전환 속도보다 스레드 간의 전환 속도가 빠르다. 멀티스레드의 다른 장점은 CPU가 여러 개일 경우에 각각의 CPU가 스레드 하나씩을 담당하는 방법으로 속도를 높일 수 있다는 것이다. 이러한 시스템에서는 여러 스레드가 실제 시간상으로 동시에 수행될 수 있기 때문이다. 멀티스레드의 단점에는 각각의 스레드 중 어떤 것이 먼저 실행될지 그 순서를 알 수 없다는 것이 있다. 예를 들어, 두 스레드가 특정 공유 변수 i의 값을 1 증가시키는 명령을 실행할 때, 다음과 같은 방식으로 수행될 수 있다. # 공유되는 변수 i의 값을 레지스터에 저장 # 레지스터의 값을 1 증가시킨다. # 변수 i에 그 값을 저장한다. 이때 두 스레드가 실행될 때 어떤 스레드가 먼저 실행될지는 보장되지 않으며, 만약 다음과 같은 순서로 실행된다면: {| class="wikitable" |- ! 스레드 !! 동작 !! i의 값 !! 스레드 1의 레지스터 !! 스레드 2의 레지스터 |- | 스레드 1 || i의 값을 레지스터에 저장 || 0 || 0 || |- | 스레드 1 || 레지스터 값을 1 증가 || 0 || 1 || |- | 스레드 1 || i에 값 저장 || **1** || 1 || |- | 스레드 2 || i의 값을 레지스터에 저장 || 1 || 1 || 1 |- | 스레드 2 || 레지스터 값을 1 증가 || 1 || 1 || 2 |- | 스레드 2 || i에 값 저장 || **2** || 1 || 2 |} 최종 결과로 i는 2가 증가된다. 하지만 다음과 같이 실행된다면: {| class="wikitable" |- ! 스레드 !! 동작 !! i의 값 !! 스레드 1의 레지스터 !! 스레드 2의 레지스터 |- | 스레드 1 || i의 값을 레지스터에 저장 || 0 || 0 || |- | 스레드 2 || i의 값을 레지스터에 저장 || 0 || 0 || 0 |- | 스레드 1 || 레지스터 값을 1 증가 || 0 || 1 || 0 |- | 스레드 2 || 레지스터 값을 1 증가 || 0 || 1 || 1 |- | 스레드 1 || i에 값 저장 || **1** || 1 || 1 |- | 스레드 2 || i에 값 저장 || **1** || 1 || 1 |} 최종 결과로 i는 1이 증가되고, 이것은 원래 프로그램의 의도(각각의 스레드가 i를 1씩 증가하는 동작)와 다를 수 있다. 또한 이러한 문제는 스레드의 실행 조건에 따라 결과가 다르게 나오므로, 오류가 발생했을 때 원인을 찾기가 힘들다. 이러한 문제를 경쟁 조건이라고 하며, 문제를 막기 위해 세마포어와 같은 방법을 통해 공유 데이터에 접근하는 스레드의 개수를 한개 이하로 유지하는 방법을 사용할 수 있다.
# 스레드의 종류
스레드를 지원하는 주체에 따라 2가지로 나눌 수 있다.
## 사용자 레벨 스레드 (User-Level Thread)
사용자 스레드는 커널 영역의 상위에서 지원되며 일반적으로 사용자 레벨의 라이브러리를 통해 구현되며, 라이브러리는 스레드의 생성 및 스케줄링 등에 관한 관리 기능을 제공한다. 동일한 메모리 영역에서 스레드가 생성 및 관리되므로 속도가 빠른 장점이 있는 반면, 여러 개의 사용자 스레드 중 하나의 스레드가 시스템 호출 등으로 중단되면 나머지 모든 스레드 역시 중단되는 단점이 있다. 이는 커널이 프로세스 내부의 스레드를 인식하지 못하며 해당 프로세스를 대기 상태로 전환시키기 때문이다.
## 커널 레벨 스레드 (Kernel-Level Thread)
커널 스레드는 운영체제가 지원하는 스레드 기능으로 구현되며, 커널이 스레드의 생성 및 스케줄링 등을 관리한다. 스레드가 시스템 호출 등으로 중단되더라도, 커널은 프로세스 내의 다른 스레드를 중단시키지 않고 계속 실행시켜준다. 다중처리기 환경에서 커널은 여러 개의 스레드를 각각 다른 처리기에 할당할 수 있다. 다만, 사용자 스레드에 비해 생성 및 관리하는 것이 느리다.
# 스레드 데이터

## 스레드 기본 데이터
스레드도 프로세스와 마찬가지로 하나의 실행 흐름이므로 실행과 관련된 데이터가 필요하다. 일반적으로 스레드는 자신만의 고유한 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택을 가진다. 코드, 데이터, 파일 등 기타 자원은 프로세스 내의 다른 스레드와 공유한다.
## 스레드 특정 데이터
위의 기본 데이터 외에도 하나의 스레드에만 연관된 데이터가 필요한 경우가 있는데, 이런 데이터를 **스레드 특정 데이터**(Thread-Specific Data, 줄여서 TSD)라고 한다. 멀티스레드 프로그래밍 환경에서 모든 스레드는 프로세스의 데이터를 공유하고 있지만, 특별한 경우에는 개별 스레드만의 자료 공간이 필요하다. 예를 들어 여러 개의 트랜잭션을 스레드로 처리할 경우, 각각의 트랜잭션 ID를 기억하고 있어야 하는데, 이때 TSD가 필요하다. TSD는 여러 스레드 라이브러리들이 지원하는 기능 중의 하나이다.
# 프로세스 관리의 변화
멀티스레드 환경이 확산됨에 따라 전통적인 프로세스 관리 방식에도 변화가 필요해졌다. 예를 들어, fork 또는 exec와 같은 시스템 호출시에 어떻게 처리할 것인가 하는 문제가 대두된 것이다. * fork 문제 : 어떤 프로세스 내의 스레드가 fork를 호출하면 모든 스레드를 가진 프로세스를 생성할 것인지, 아니면 fork를 요청한 스레드만 가진 프로세스를 생성할 것인지 하는 문제이다. 유닉스에서는 각각 2가지 버전의 fork를 지원하고 있다. * exec 문제 : fork를 통해 모든 스레드를 복제하고 난 후, exec를 수행한다면 모든 스레드들이 초기화된다. 그렇다면 교체될 스레드를 복제하는 작업은 필요가 없기 때문에 애초에 fork를 요청한 스레드만을 복제했어야 한다. 한편, fork를 한 후에 exec를 수행하지 않는다면 모든 스레드를 복제할 필요가 있는 경우도 있다