본문으로 이동

스레드 (컴퓨팅)

위키백과, 우리 모두의 백과사전.
전자 게시판의 종류에 대해서는 스레드 플로트형 게시판 문서를 참고하십시오.
두 개의 스레드가 하나의 프로세서에서 실행 중인 프로세스
프로그램 vs. 프로세스 vs. 스레드
스케줄링, 선점, 문맥 교환

컴퓨터 과학에서 실행 스레드(thread)는 일반적으로 운영체제의 일부인 스케줄러가 독립적으로 관리할 수 있는 가장 작은 프로그래밍된 명령어 시퀀스이다.[1] 많은 경우, 스레드는 프로세스의 구성 요소이다.

특정 프로세스의 여러 스레드는 병렬적으로(멀티스레딩 기능을 통해) 실행될 수 있으며, 메모리와 같은 리소스를 공유하지만, 서로 다른 프로세스는 이러한 리소스를 공유하지 않는다. 특히 프로세스의 스레드들은 실행 가능한 코드와 동적 할당된 변수 값, 스레드 로컬이 아닌 전역 변수들을 특정 시점에 공유한다.

스레드와 프로세스의 구현 방식은 운영체제마다 다르다.[2]

역사

[편집]

스레드는 1967년 IBM의 일괄 처리 운영체제인 OS/360에서 "태스크(tasks)"라는 이름으로 초기 형태가 등장했다. 이 시스템은 사용자에게 세 가지 OS/360 제어 시스템 구성을 제공했으며, 그중 하나가 가변 태스크 수의 멀티프로그래밍(MVT)이었다. 살처(1966)는 "스레드"라는 용어를 처음 사용한 사람으로 빅터 A. 비소츠키를 지목했다.[3]

Mach의 스레드 구현은 1986년 여름에 기술되었다.[4] 1987년에 출시된 OS/2 1.0은 스레드를 지원했다.[5] 디지털 리서치의 MP/M 운영체제는 1979년 인텔 8080 프로세서에서, 1981년 8086 프로세서에서 멀티프로그래밍, 멀티태스킹, 선점형 멀티태스킹을 제공했다. 스레드를 지원한 최초의 윈도우 버전은 1993년에 출시된 윈도우 NT였다.

1995년 IEEE는 다양한 유닉스 계열 운영체제에서 이식 가능한 멀티스레드 프로그래밍을 위한 표준 인터페이스인 POSIX 스레드(pthreads) API를 정의했다.[6] 그 이후로 pthreads는 기존 윈도우 API 위에서 표준을 구현하는 pthreads-w32와 같은 타사 패키지를 통해 윈도우에서도 구현되었다.[7]

2000년대 초반 CPU가 멀티코어를 활용하기 시작하면서 소프트웨어 애플리케이션에서 스레드 사용이 더 보편화되었다. 성능 향상을 위해 멀티코어를 활용하려는 애플리케이션들은 병행성을 사용하여 멀티코어를 활용해야 했다.[8]

관련 개념

[편집]

스케줄링은 커널 수준 또는 사용자 수준에서 수행될 수 있으며, 멀티태스킹은 선점형 또는 협동형으로 수행될 수 있다. 이로 인해 다양한 관련 개념이 생성된다.

프로세스

[편집]

커널 수준에서 프로세스는 하나 이상의 커널 스레드를 포함하며, 이는 메모리 및 파일 핸들과 같은 프로세스의 리소스를 공유한다. 즉, 프로세스는 리소스의 단위이고, 스레드는 스케줄링과 실행의 단위이다. 커널 스케줄링은 일반적으로 선점형으로 수행되거나, 드물게 협동형으로 수행된다. 사용자 수준에서는 런타임 시스템과 같은 프로세스가 자체적으로 여러 실행 스레드를 스케줄링할 수 있다. 얼랭과 같이 데이터를 공유하지 않는 경우 이들을 보통 프로세스라고 부르며,[9] 데이터를 공유하는 경우에는 특히 선점형으로 스케줄링될 때 (사용자) 스레드라고 부른다. 협동형으로 스케줄링되는 사용자 스레드를 파이버라고 한다. 사용자 스레드는 여러 방식(일대일, 다대일, 다대다)으로 커널 스레드에 의해 실행될 수 있다. 경량 프로세스라는 용어는 사용자 스레드나 사용자 스레드를 커널 스레드에 스케줄링하기 위한 커널 메커니즘을 의미한다.

프로세스는 커널 스케줄링의 헤비웨이트 단위인데, 이는 프로세스를 생성, 파괴 및 전환하는 비용이 상대적으로 비싸기 때문이다. 프로세스는 운영체제에 의해 할당된 리소스를 소유한다. 리소스에는 (코드 및 데이터용) 메모리, 파일 핸들, 소켓, 장치 핸들, 윈도우 및 프로세스 제어 블록이 포함된다. 프로세스는 프로세스 격리를 통해 격리되며, 파일 핸들 상속이나 공유 메모리 세그먼트와 같은 명시적인 방법을 통하지 않고는 주소 공간이나 파일 리소스를 공유하지 않는다. 프로세스 생성이나 파괴는 리소스를 획득하거나 해제해야 하므로 상대적으로 비용이 많이 든다. 프로세스는 일반적으로 선점형으로 멀티태스킹되며, 프로세스 전환은 캐시 플러싱(특히 프로세스 전환이 가상 메모리 주소 지정을 변경하여 태깅되지 않은 변환 색인 버퍼(TLB)가 무효화되어 플러싱됨)과 같은 문제로 인해 문맥 교환의 기본 비용 이상으로 비용이 많이 든다.

커널 스레드

[편집]

커널 스레드는 커널 스케줄링의 경량 단위이다. 각 프로세스 내에는 적어도 하나의 커널 스레드가 존재한다. 프로세스 내에 여러 커널 스레드가 존재하면 동일한 메모리 및 파일 리소스를 공유한다. 운영체제의 프로세스 스케줄러가 선점형이면 커널 스레드도 선점형으로 멀티태스킹된다. 커널 스레드는 스택, 프로그램 카운터를 포함한 프로세서 레지스터의 복사본, (있는 경우) 스레드 로컬 스토리지를 제외하고는 리소스를 소유하지 않으므로 생성 및 파괴 비용이 상대적으로 저렴하다. 스레드 전환 또한 상대적으로 저렴하다. 문맥 교환(레지스터 및 스택 포인터 저장 및 복원)이 필요하지만 가상 메모리는 변경하지 않으므로 캐시 친화적(TLB 유효 유지)이다. 커널은 CPU의 각 코어에 하나 이상의 소프트웨어 스레드를 할당할 수 있으며, 차단된 스레드를 교체할 수 있다. 그러나 커널 스레드는 사용자 스레드보다 교체되는 데 훨씬 오래 걸린다.

사용자 스레드

[편집]

스레드는 때때로 사용자 공간 라이브러리로 구현되므로 사용자 스레드라고 불린다. 커널은 이를 인식하지 못하므로 사용자 공간에서 관리 및 스케줄링된다. 일부 구현은 멀티프로세서 기계의 이점을 얻기 위해 여러 커널 스레드 위에 사용자 스레드를 기반으로 한다(M:N 모델). 가상 머신에 의해 구현된 사용자 스레드를 그린 스레드라고도 한다.

사용자 스레드 구현은 일반적으로 완전히 사용자 공간에 있으므로, 동일한 프로세스 내의 사용자 스레드 간 문맥 교환은 커널과 전혀 상호 작용할 필요가 없기 때문에 매우 효율적이다. 문맥 교환은 현재 실행 중인 사용자 스레드나 파이버가 사용하는 CPU 레지스터를 로컬에 저장한 다음 실행할 사용자 스레드나 파이버에 필요한 레지스터를 로드함으로써 수행될 수 있다. 스케줄링이 사용자 공간에서 발생하기 때문에 스케줄링 정책을 프로그램 워크로드 요구 사항에 맞게 조정하기가 더 쉽다.

그러나 사용자 스레드에서 차단 시스템 호출을 사용하는 것은 문제가 될 수 있다. 사용자 스레드나 파이버가 차단되는 시스템 호출을 수행하면, 시스템 호출이 반환될 때까지 프로세스의 다른 사용자 스레드와 파이버는 실행할 수 없다. 이 문제의 전형적인 예는 I/O를 수행할 때이다. 대부분의 프로그램은 동기적으로 I/O를 수행하도록 작성된다. I/O 작업이 시작되면 시스템 호출이 이루어지고 I/O 작업이 완료될 때까지 반환되지 않는다. 그 사이에 전체 프로세스가 커널에 의해 "차단"되어 실행될 수 없게 되며, 이는 같은 프로세스의 다른 사용자 스레드와 파이버가 실행되는 것을 방해한다.

이 문제에 대한 일반적인 해결책(특히 많은 그린 스레드 구현에서 사용)은 비차단 I/O를 내부적으로 사용하여 전체 프로세스가 아닌 호출하는 스레드만 차단하는 인터페이스를 구현하는 I/O API를 제공하고, I/O 작업이 진행되는 동안 다른 사용자 스레드나 파이버를 스케줄링하는 것이다. 다른 차단 시스템 호출에 대해서도 유사한 해결책을 제공할 수 있다. 대안으로, 프로그램은 동기식 I/O나 기타 차단 시스템 호출의 사용을 피하도록 작성될 수 있다(특히 람다 연속 및 async/await 기본 요소를 사용하여 비차단 I/O 사용[10]).

파이버

[편집]

파이버는 협동적으로 스케줄링되는 더 가벼운 스케줄링 단위이다. 실행 중인 파이버는 다른 파이버가 실행될 수 있도록 명시적으로 양보해야 하며, 이는 커널이나 사용자 스레드보다 구현을 훨씬 쉽게 만든다. 파이버는 동일한 프로세스의 모든 스레드에서 실행되도록 스케줄링될 수 있다. 이는 애플리케이션이 커널 스케줄러(애플리케이션에 맞게 조정되지 않았을 수 있음)에 의존하는 대신 직접 스케줄링을 관리하여 성능을 향상시킬 수 있도록 한다. OpenMP 병렬 프로그래밍 모델의 일부 연구 구현은 파이버를 통해 태스크를 구현한다.[11][12] 파이버와 밀접하게 관련된 것은 코루틴인데, 코루틴은 언어 수준 구성체인 반면 파이버는 시스템 수준 구성체라는 차이점이 있다.

스레드 vs 프로세스

[편집]

스레드는 다음과 같은 여러 면에서 전통적인 멀티태스킹 운영체제 프로세스와 다르다.

  • 프로세스는 일반적으로 독립적이지만, 스레드는 프로세스의 부분 집합으로 존재한다.
  • 프로세스는 스레드보다 훨씬 많은 상태 정보를 가지며, 반면 프로세스 내의 여러 스레드는 프로세스 상태뿐만 아니라 메모리 및 기타 리소스를 공유한다.
  • 프로세스는 별도의 주소 공간을 가지지만, 스레드는 주소 공간을 공유한다.
  • 프로세스는 시스템이 제공하는 프로세스 간 통신 메커니즘을 통해서만 상호 작용한다.
  • 동일한 프로세스 내의 스레드 간 문맥 교환은 일반적으로 프로세스 간 문맥 교환보다 더 빠르게 발생한다.

윈도우 NT와 OS/2와 같은 시스템은 저렴한 스레드와 비싼 프로세스를 가진다고 평가된다. 다른 운영체제에서는 주소 공간 전환 비용을 제외하고는 큰 차이가 없으며, 일부 아키텍처(특히 X86)에서는 변환 색인 버퍼(TLB) 플러시가 발생한다.

스레드와 프로세스의 장단점은 다음과 같다.

  • 스레드의 낮은 리소스 소비: 스레드를 사용하면 애플리케이션은 여러 프로세스를 사용할 때보다 더 적은 리소스로 작동할 수 있다.
  • 스레드의 간편한 공유 및 통신: 프로세스 간 통신(IPC)을 수행하기 위해 메시지 전달이나 공유 메모리 메커니즘이 필요한 프로세스와 달리, 스레드는 이미 공유하고 있는 데이터, 코드 및 파일을 통해 통신할 수 있다.
  • 프로세스를 충돌시키는 스레드: 스레드가 동일한 주소 공간을 공유하기 때문에 스레드에 의해 수행된 잘못된 작업이 전체 프로세스를 충돌시킬 수 있다. 따라서 문제를 일으키는 하나의 스레드가 애플리케이션의 다른 모든 스레드 처리를 방해할 수 있다.

스케줄링

[편집]

선점형 vs 협동형 스케줄링

[편집]

운영체제는 스레드를 선점형 또는 협동형으로 스케줄링한다. 다중 사용자 운영체제는 문맥 교환을 통해 실행 시간에 대한 세밀한 제어가 가능하므로 일반적으로 선점형 멀티스레딩을 선호한다. 그러나 선점형 스케줄링은 프로그래머가 예상하지 못한 순간에 스레드를 문맥 교환할 수 있으며, 이는 우선순위 역전, 우선순위 역전 또는 기타 부작용을 일으킬 수 있다. 반면 협동형 멀티스레딩은 스레드가 실행 제어권을 포기하는 것에 의존하며, 이를 통해 스레드가 완료될 때까지 실행되도록 보장한다. 이는 협동형으로 멀티태스킹된 스레드가 리소스를 기다리면서 차단되거나 집중적인 계산 중에 실행 제어권을 양보하지 않아 다른 스레드를 굶주리게 할 경우 문제를 일으킬 수 있다.

단일 vs 멀티프로세서 시스템

[편집]

2000년대 초반까지 대부분의 데스크톱 컴퓨터는 단일 코어 CPU만 탑재하고 있었으며, 하드웨어 스레드에 대한 지원도 없었다. 하지만 스레드 간 전환이 일반적으로 전체 프로세스 문맥 교환보다 빨랐기 때문에 그러한 컴퓨터에서도 스레드가 사용되었다. 2002년 인텔은 펜티엄 4 프로세서에 하이퍼스레딩이라는 이름으로 동시 멀티스레딩 지원을 추가했으며, 2005년에는 듀얼 코어 펜티엄 D 프로세서를 도입했고 AMD는 듀얼 코어 애슬론 64 X2 프로세서를 도입했다.

단일 프로세서를 갖춘 시스템은 일반적으로 시간 분할을 통해 멀티스레딩을 구현한다. 중앙 처리 장치(CPU)는 서로 다른 소프트웨어 스레드 사이를 전환한다. 이 문맥 교환은 보통 사용자가 스레드나 태스크가 병렬로 실행되는 것처럼 인식할 만큼 빈번하게 발생한다(인기 있는 서버/데스크톱 운영체제의 경우, 다른 스레드가 대기 중일 때 스레드의 최대 시간 분할은 종종 100~200ms로 제한됨). 멀티프로세서 또는 멀티코어 시스템에서는 여러 스레드가 병렬로 실행될 수 있으며, 모든 프로세서나 코어가 별도의 스레드를 동시에 실행한다. 하드웨어 스레드가 있는 프로세서나 코어에서는 별도의 소프트웨어 스레드도 별도의 하드웨어 스레드에 의해 동시에 실행될 수 있다.

스레딩 모델

[편집]

1:1 (커널 수준 스레딩)

[편집]

사용자가 생성한 스레드와 커널의 스케줄링 가능한 엔티티가 1:1로 대응하는 방식[13]은 가장 단순한 스레딩 구현이다. OS/2와 Win32는 처음부터 이 방식을 사용했으며, 리눅스에서는 GNU C 라이브러리가 (NPTL 또는 이전의 LinuxThreads를 통해) 이 방식을 구현한다. 이 방식은 솔라리스, NetBSD, FreeBSD, macOS, iOS에서도 사용된다.

M:1 (사용자 수준 스레딩)

[편집]

M:1 모델은 모든 애플리케이션 수준 스레드가 하나의 커널 수준 스케줄링 엔티티에 매핑됨을 의미한다.[13] 커널은 애플리케이션 스레드에 대해 전혀 알지 못한다. 이 방식에서는 문맥 교환이 매우 빠르게 수행될 수 있으며, 스레딩을 지원하지 않는 단순한 커널에서도 구현될 수 있다. 그러나 주요 단점 중 하나는 멀티스레드 프로세서나 멀티프로세서 컴퓨터의 하드웨어 가속 이점을 얻을 수 없다는 것이다. 즉, 동시에 스케줄링되는 스레드가 하나뿐이다.[13] 예를 들어, 스레드 중 하나가 I/O 요청을 실행해야 하는 경우 전체 프로세스가 차단되고 스레딩 이점을 사용할 수 없다. GNU Portable Threads와 State Threads는 사용자 수준 스레딩을 사용한다.

M:N (하이브리드 스레딩)

[편집]

M:N은 M개의 애플리케이션 스레드를 N개의 커널 엔티티(또는 "가상 프로세서")에 매핑한다.[13] 이는 커널 수준("1:1")과 사용자 수준("N:1") 스레딩 사이의 타협안이다. 일반적으로 "M:N" 스레딩 시스템은 커널 코드와 사용자 공간 코드 모두를 변경해야 하므로 커널 스레드나 사용자 스레드보다 구현하기가 훨씬 복잡하다. M:N 구현에서 스레딩 라이브러리는 가용한 스케줄링 가능 엔티티에 사용자 스레드를 스케줄링할 책임을 진다. 이를 통해 시스템 호출을 피할 수 있어 스레드의 문맥 교환이 매우 빨라진다. 그러나 이는 복잡성을 증가시키고 우선순위 역전 가능성을 높이며, 사용자 공간 스케줄러와 커널 스케줄러 간의 광범위하고 비용이 많이 드는 조정 없이는 최적화되지 않은 스케줄링을 초래할 수 있다.

하이브리드 구현 예시

[편집]

유닉스 시스템의 스레딩 모델 역사

[편집]

SunOS 4.x는 경량 프로세스 또는 LWP를 구현했다. NetBSD 2.x+ 및 DragonFly BSD는 LWP를 커널 스레드로 구현한다(1:1 모델). SunOS 5.2부터 SunOS 5.8까지, 그리고 NetBSD 2부터 NetBSD 4까지는 각 커널 스레드에서 하나 이상의 사용자 수준 스레드를 다중화하는 2계층 모델(M:N 모델)을 구현했다. SunOS 5.9 이후와 NetBSD 5는 사용자 스레드 지원을 제거하고 1:1 모델로 복귀했다.[14] FreeBSD 5는 M:N 모델을 구현했다. FreeBSD 6은 1:1과 M:N을 모두 지원했으며 사용자는 /etc/libmap.conf를 사용하여 특정 프로그램에 사용할 모델을 선택할 수 있었다. FreeBSD 7부터는 1:1이 기본값이 되었다. FreeBSD 8은 더 이상 M:N 모델을 지원하지 않는다.

단일 스레드 vs 멀티스레드 프로그램

[편집]

컴퓨터 프로그래밍에서 단일 스레딩은 한 번에 하나의 기계어 명령어를 처리하는 것이다.[15] 변수의 의미론과 프로세스 상태의 공식 분석에서 단일 스레딩이라는 용어는 다르게 사용되어 함수형 프로그래밍 커뮤니티에서 흔히 볼 수 있는 "단일 스레드 내에서의 역추적"을 의미할 수 있다.[16]

멀티스레딩은 주로 다중작업 운영체제에서 발견된다. 멀티스레딩은 하나의 프로세스 컨텍스트 내에 여러 스레드가 존재할 수 있도록 하는 널리 퍼진 프로그래밍 및 실행 모델이다. 이러한 스레드들은 프로세스의 리소스를 공유하지만 독립적으로 실행될 수 있다. 스레드 프로그래밍 모델은 개발자에게 병행 실행에 대한 유용한 추상화를 제공한다. 멀티스레딩은 또한 멀티프로세싱 시스템에서 병렬 실행을 활성화하기 위해 하나의 프로세스에 적용될 수 있다.

멀티스레딩 라이브러리는 일반적으로 함수를 매개변수로 받아 새 스레드를 생성하는 함수 호출을 제공한다. 그러면 전달된 함수를 실행하기 시작하고 함수가 반환될 때 종료되는 병행 스레드가 생성된다. 스레드 라이브러리는 데이터 동기화 기능도 제공한다.

스레드와 데이터 동기화

[편집]

동일한 프로세스의 스레드들은 동일한 주소 공간을 공유한다. 이를 통해 병행 실행되는 코드가 긴밀하게 결합되고 프로세스 간 통신의 오버헤드나 복잡성 없이 편리하게 데이터를 교환할 수 있다. 그러나 스레드 간에 공유될 때, 단순한 데이터 구조조차도 업데이트하는 데 둘 이상의 CPU 명령어가 필요한 경우 경쟁 상태에 취약해진다. 두 스레드가 동시에 데이터 구조를 업데이트하려고 시도하다가 예기치 않게 변경되는 것을 발견할 수 있다. 경쟁 상태로 인한 버그는 재현하고 격리하기가 매우 어려울 수 있다.

이를 방지하기 위해 스레딩 애플리케이션 프로그래밍 인터페이스(API)는 동시 접근으로부터 데이터 구조를 잠금하기 위해 뮤텍스와 같은 동기화 기본 요소를 제공한다. 단일 프로세서 시스템에서 잠긴 뮤텍스를 마주친 스레드는 잠들어야 하며 따라서 문맥 교환을 트리거한다. 멀티프로세서 시스템에서는 스레드가 스핀락에서 뮤텍스를 폴링할 수 있다. 이 두 경우 모두 성능을 저하시키고 특히 잠금 세분성이 너무 세밀할 경우 대칭형 다중 처리(SMP) 시스템의 프로세서들이 메모리 버스를 놓고 경쟁하게 만들 수 있다.

기타 동기화 API에는 조건 변수, 임계 구역, 세마포어, 모니터가 포함된다.

스레드 풀

[편집]

스레드와 관련된 인기 있는 프로그래밍 패턴은 시작 시 설정된 수의 스레드가 생성되어 태스크가 할당되기를 기다리는 스레드 풀 패턴이다. 새로운 태스크가 도착하면 스레드가 깨어나 태스크를 완료하고 다시 대기 상태로 돌아간다. 이는 수행되는 모든 태스크에 대해 비용이 많이 드는 스레드 생성 및 파괴 함수를 피하고, 스레드 관리 책임을 애플리케이션 개발자에게서 라이브러리나 스레드 관리에 더 적합한 운영체제로 넘긴다.

모든 스레드가 바쁘면 스레드를 사용할 수 있을 때까지 추가 작업 항목이 큐에 배치된다.[17]

멀티스레드 프로그램 vs 단일 스레드 프로그램 장단점

[편집]

멀티스레드 애플리케이션은 단일 스레드 애플리케이션에 비해 다음과 같은 장점이 있다.

  • 응답성: 멀티스레딩을 통해 애플리케이션이 입력에 계속 응답할 수 있다. 단일 스레드 프로그램에서 메인 실행 스레드가 오래 걸리는 작업으로 인해 차단되면 전체 애플리케이션이 멈춘 것처럼 보일 수 있다. 이러한 오래 걸리는 작업을 메인 실행 스레드와 병행하여 실행되는 워커 스레드로 옮기면 백그라운드에서 작업을 실행하면서도 애플리케이션이 사용자 입력에 계속 응답할 수 있다. 반면에 대부분의 경우 멀티스레딩만이 프로그램의 응답성을 유지하는 유일한 방법은 아니며, 비차단 I/O 및 유닉스 시그널을 사용하여 유사한 결과를 얻을 수 있다.[18]
  • 병렬화: 멀티코어 또는 멀티 CPU 시스템을 사용하려는 애플리케이션은 멀티스레딩을 사용하여 데이터와 태스크를 병렬 하위 태스크로 나누고 기반 아키텍처가 스레드 실행 방식을 관리하도록 할 수 있다. 즉, 하나의 코어에서 병행하거나 여러 코어에서 병렬로 실행하는 것이다. CUDA, OpenCL, DirectX 12와 같은 GPU 컴퓨팅 환경은 수십에서 수백 개의 스레드가 데이터 전반에 걸쳐 병렬로 많은 수의 코어 위에서 실행되는 멀티스레딩 모델을 사용한다. 이는 시스템 활용도를 높이고 (동기화 비용이 이점을 상쇄하지 않는다는 전제하에) 프로그램 실행 속도를 빠르게 할 수 있다.

멀티스레드 애플리케이션은 다음과 같은 단점이 있다.

  • 동기화 복잡성 및 관련 버그: 스레드 프로그램의 전형인 공유 리소스를 사용할 때 프로그래머는 경쟁 상태 및 기타 직관적이지 않은 동작을 피하기 위해 주의해야 한다. 데이터가 올바르게 조작되려면 데이터를 올바른 순서로 처리하기 위해 스레드들이 시간에 맞춰 랑데부해야 하는 경우가 많다. 또한 스레드는 공통 데이터가 한 스레드에서 수정되는 동안 다른 스레드에서 읽거나 덮어쓰이지 않도록 상호 배제 작업(보통 뮤텍스를 사용하여 구현됨)이 필요할 수 있다. 이러한 기본 요소를 부주의하게 사용하면 교착 상태, 라이브락 또는 리소스를 두고 경쟁이 발생할 수 있다. 또한 멀티스레딩은 RAM을 포함한 시스템 리소스 사용량을 증가시킬 수 있다.[19] 에드워드 A. 리가 썼듯이: "스레드는 순차적 계산에서 작은 단계처럼 보이지만 사실 거대한 단계를 나타낸다. 스레드는 순차적 계산의 가장 본질적이고 매력적인 속성인 이해 가능성, 예측 가능성, 결정론을 버린다. 계산 모델로서의 스레드는 매우 비결정적이며, 프로그래머의 작업은 그러한 비결정론을 솎아내는 것이 된다."[20]
  • 테스트 불가능함: 일반적으로 멀티스레드 프로그램은 비결정적이므로 테스트할 수 없다. 다시 말해, 멀티스레드 프로그램은 테스트 시스템에서는 절대 나타나지 않고 프로덕션 환경에서만 나타나는 버그를 쉽게 가질 수 있다.[21][20] 이는 스레드 간 통신을 특정하고 잘 정의된 패턴(예: 메시지 전달)으로 제한함으로써 완화될 수 있다.
  • 동기화 비용: 최신 CPU에서 스레드 문맥 교환은 최대 100만 CPU 사이클까지 비용이 들 수 있으므로[22] 효율적인 멀티스레딩 프로그램을 작성하는 것은 어렵다. 특히 스레드 간 동기화가 너무 빈번하지 않도록 주의를 기울여야 한다.

프로그래밍 언어 지원

[편집]

많은 프로그래밍 언어가 어느 정도 수준에서 스레딩을 지원한다.

  • IBM PL/I(F)는 이미 1960년대 후반에 멀티스레딩(멀티태스킹이라고 함) 지원을 포함했으며, 이는 최적화 컴파일러 및 이후 버전에서도 계속되었다. IBM 엔터프라이즈 PL/I 컴파일러는 새로운 모델의 "스레드" API를 도입했다. 두 버전 모두 PL/I 표준의 일부는 아니었다.
  • C 및 C++의 많은 구현은 스레딩을 지원하며 운영체제의 네이티브 스레딩 API에 대한 접근을 제공한다. 스레드 구현을 위한 표준화된 인터페이스는 C-함수 라이브러리 호출 집합인 POSIX 스레드(Pthreads)이다. 운영체제 공급업체는 원하는 대로 인터페이스를 구현할 수 있지만, 애플리케이션 개발자는 여러 플랫폼에서 동일한 인터페이스를 사용할 수 있어야 한다. 리눅스를 포함한 대부분의 유닉스 플랫폼은 Pthreads를 지원한다. 마이크로소프트 윈도우는 Process.h 인터페이스에 beginthread와 같은 멀티스레딩용 스레드 함수 세트를 자체적으로 가지고 있다.
  • 자바, 파이썬, 닷넷 프레임워크 언어와 같은 일부 고급 (그리고 일반적으로 크로스 플랫폼인) 언어들은 런타임에서 스레딩 구현의 플랫폼별 차이를 추상화하면서 개발자에게 스레딩을 노출한다. 다른 여러 프로그래밍 언어 및 언어 확장들도 개발자에게 병행성과 스레딩 개념을 완전히 추상화하려고 시도한다(Cilk, OpenMP, 메시지 전달 인터페이스(MPI)). 일부 언어들은 병행성이나 스레드를 요구하지 않고 대신 순차적 병렬성을 위해 설계되었다(특히 GPU 사용 시) (Ateji PX, CUDA).
  • 몇몇 인터프리터 프로그래밍 언어는 글로벌 인터프리터 락(GIL)으로 인해 스레딩과 병행성은 지원하지만 스레드의 병렬 실행은 지원하지 않는 구현(예: 루비용 루비 MRI, 파이썬용 C파이썬)을 가지고 있다. GIL은 인터프리터가 한 번에 두 개 이상의 스레드에서 애플리케이션 코드를 동시에 해석하는 것을 방지할 수 있는 인터프리터에 의해 유지되는 상호 배제 락이다. 이는 멀티코어 시스템에서의 병렬성을 효과적으로 제한한다. 또한 프로세서 바운드 스레드(프로세서가 필요한 스레드)에 대한 성능을 제한하지만, I/O 바운드나 네트워크 바운드 스레드에는 그렇게 큰 영향을 미치지 않는다. Thread 확장을 사용하는 Tcl과 같이 인터프리터 프로그래밍 언어의 다른 구현들은 데이터와 코드를 스레드 간에 명시적으로 "공유"해야 하는 아파트먼트 모델을 사용하여 GIL 제한을 피한다. Tcl에서 각 스레드는 하나 이상의 인터프리터를 가진다.
  • 데이터 병렬 컴퓨팅을 위해 설계된 CUDA와 같은 프로그래밍 모델에서 스레드 배열은 메모리에서 데이터를 찾기 위해 ID만을 사용하여 동일한 코드를 병렬로 실행한다. 본질적으로, 애플리케이션은 각 스레드가 서로 다른 메모리 세그먼트에서 동일한 작업을 수행하여 병렬로 작동하고 GPU 아키텍처를 사용할 수 있도록 설계되어야 한다.
  • 베릴로그와 같은 하드웨어 기술 언어는 매우 많은 수의 스레드를 지원하는(하드웨어 모델링용) 다른 스레딩 모델을 가지고 있다.

같이 보기

[편집]

각주

[편집]
  1. ↑ Lamport, Leslie (September 1979). “How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs” (PDF). 《IEEE Transactions on Computers》 C–28 (9): 690–691. Bibcode:1979ITCmp.100..690L. doi:10.1109/tc.1979.1675439. S2CID 5679366.
  2. ↑ Tanenbaum, Andrew S. (1992). 《Modern Operating Systems》. Prentice-Hall International Editions. ISBN 0-13-595752-4.
  3. ↑ Saltzer, Jerome Howard (July 1966). 《Traffic Control in a Multiplexed Computer System》 (PDF) (학위논문). 20쪽.
  4. ↑ 〈Mach: A New Kernel Foundation for UNIX Development〉 (PDF). 《1986 Summer USENIX Technical Conference & Exhibition Proceedings》. 1986. 93쪽.
  5. ↑ “Microsoft Operating System/2 Programmer's Guide” (PDF).
  6. ↑ “Standard for Information Technology--Portable Operating System Interface (POSIX(™)) - System Application Program Interface (API) Amendment 2: Threads Extension (C Language)” (영어). 《IEEE Standards Association》. 2025년 12월 11일에 확인함.
  7. ↑ “Pthread Win-32: Level of standards conformance”. 2006년 12월 22일. 2010년 6월 11일에 원본 문서에서 보존된 문서. 2010년 8월 29일에 확인함.
  8. ↑ Sutter, Herb (March 2005). “The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software”. 《닥터 돕스 저널》 30 (3).
  9. ↑ “Erlang: 3.1 Processes”.
  10. ↑ Ignatchenko, Sergey. 《Eight Ways to Handle Non-blocking Returns in Message-passing Programs: from C++98 via C++11 to C++20》. CPPCON. 2020년 11월 25일에 원본 문서에서 보존된 문서. 2020년 11월 24일에 확인함.
  11. ↑ Ferat, Manuel; Pereira, Romain; Roussel, Adrien; Carribault, Patrick; Steffenel, Luiz-Angelo; Gautier, Thierry (September 2022). 〈Enhancing MPI+OpenMP Task Based Applications for Heterogeneous Architectures with GPU support〉 (PDF). 《OpenMP in a Modern World: From Multi-device Support to Meta Programming.》. IWOMP 2022: 18th International Workshop on OpenMP. Lecture Notes in Computer Science. 3–16쪽. doi:10.1007/978-3-031-15922-0_1. ISBN 978-3-031-15921-3. S2CID 251692327.
  12. ↑ Iwasaki, Shintaro; Amer, Abdelhalim; Taura, Kenjiro; Seo, Sangmin; Balaji, Pavan. 《BOLT: Optimizing OpenMP Parallel Regions with User-Level Threads》 (PDF). The 28th International Conference on Parallel Architectures and Compilation Techniques.
  13. 1 2 3 4 Silberschatz, Abraham; Galvin, Peter Baer; Gagne, Greg (2013). 《Operating system concepts》 9판. Hoboken, N.J.: Wiley. 170–171쪽. ISBN 9781118063330.
  14. ↑ “Multithreading in the Solaris Operating Environment” (PDF). 2002. 2009년 2월 26일에 원본 문서 (PDF)에서 보존된 문서.
  15. ↑ Menéndez, Raúl; Lowe, Doug (2001). 《Murach's CICS for the COBOL Programmer》. Mike Murach & Associates. 512쪽. ISBN 978-1-890774-09-7.
  16. ↑ 틀:서적 인주
  17. ↑ “The managed thread pool - .NET Microsoft Learn”. Microsoft Learn. 2026년 4월 22일에 확인함.
  18. ↑ Ignatchenko, Sergey (August 2010). “Single-Threading: Back to the Future?”. 《오버로드》 (ACCU) (97): 16–19.
  19. ↑ Weisner, Rickey C. (March 2012). “How Memory Allocation Affects Performance in Multithreaded Programs”.
  20. 1 2 Lee, Edward (2006년 1월 10일). “The Problem with Threads”. UC 버클리.
  21. ↑ Ignatchenko, Sergey (August 2015). “Multi-threading at Business-logic Level is Considered Harmful”. 《오버로드》 (ACCU) (128): 4–7.
  22. ↑ 'No Bugs' Hare (2016년 9월 12일). “Operation Costs in CPU Clock Cycles”.

외부 링크

[편집]