스레드는 CPU 이용의 기본 단위로 스레드ID, PC, 레지스터 집합, 스택으로 이루어져있다. 같은 프로세스에 속한 스레드끼리는 운영체제 자원들을 공유한다.
프로세스 메모리 영역 스레드간의 공유 여부
공유 | 개별 | |
---|---|---|
CODE | O | X |
DATA | O | X |
STACK | X | O |
현대의 컴퓨터와 모바일 기기에서 작동하는 거의 모든 응용 소프트웨어는 다중 스레드를 이용하며 다중 처리 시스템에서 다수의 CPU 작업을 여러개의 스레드로 병렬로 처리할 수 있다.
- 기존 프로세스와 새로 생성한 프로세스가 해야할 일이 동일하다면 같은 프로세스를 만드는 것보다 프로세스 내에 여러 스레드 만드는 것이 더 효율적이다.
- 응답성
- 응용 프로그램의 일부가 봉쇄되거나 긴 작업을 수행하더라도 프로그램의 수행이 계속되기에 응답성을 증가 시킨다.
- 자원 공유
- 스레드는 자동으로 속한 프로세스의 자원, 메모리를 공유하기에 IPC 를 이용하지 않아도 된다.
- 경제성
- 프로세스 생성은 많은 비용이 들고 스레드간의 문맥 교환이 프로세스간의 문맥 교환보다 더 빠르다.
- 규모 적응성
- 다중 처리기 구조에서 각각의 스레드는 병렬로 수행될 수 있다.
단일 컴퓨터 칩에 여러 컴퓨터 코어를 배치한 시스템을 뜻하며 다중 스레드 프로그래밍은 이를 효율적 으로 사용하며 병행성을 향상시키는 기법을 제공한다.
- 병행 시스템 : 모든 작업이 진행되게 하여 둘 이상의 작업을 지원 ( 시분할 )
- 병렬 시스템 : 둘 이상의 작업을 동시에 수행한다.
다중 코어 시스템을 위해 프로그래밍하기 위해 극복해야할 도전 과제들이 있다.
- 테스크 인식 : 응용 프로그램을 분석하여 독립된 병행 가능 테스트로 나눌 수 있는 영역을 찾아야 한다.
- 균형 : 전체 작업이 균등한 기여도를 가지도록 테스크를 나눈다. 기여도가 낮은 작업을 위해 별도의 코어를 사용하는 것은 가치가 없다.
- 데이터 분리 : 테스크가 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 나누어져야 한다.
- 데이터 종속성 : 데이터가 둘 이상의 테스크 사이에 종속성이 없는지 검토해야 한다.
- 시험 및 디버깅 : 다중 코어에서 병렬로 실행될 때 다양한 실행 경로에 대해 테스트, 디버깅한다.
응용 프로그램은 두 가지 전략을 혼합하여 사용할 수 있다.
동일한 데이터의 부분집합을 다수의 계산 코어에 분배하여 각 코어에서 동일한 연산을 실행하는데 초점을 맞춘다.
- N 개 배열의 원소를 다 더할 때 N/2 개씩 나눠 병렬로 연산을 실행할 때
데이터가 아닌 스레드를 다수의 코어에 분배하여 연산을 실행하는 것
사용자 스레드는 커널 위에서 지원 후 커널의 지원 없이 관리되고 커널 스레드는 운영체제에 의해 직접 지원되고 관리된다.
하나의 커널 스레드 - 많은 사용자 스레드
- 한번에 하나의 스레드만 커널에 접근할 수 있기에 다중 스레드가 병렬로 실행될 수 없다.
- 다중 처리 코어의 이점을 살릴 수 없어 현재는 거의 존재하지 않는다.
하나의 커널 스레드 - 하나의 사용자 스레드
- 다중 처리기에서 다중 스레드가 병렬로 수행될 수 있다.
- 사용자 스레드를 만들때마다 커널 스레드를 생성하기에 많은 수의 커널 스레드가 성능에 부담을 줄 수 있다.
- Linux, Windows 가 해당 모델을 구현한다.
여러개의 사용자 스레드 - 같거나 작은 여러개의 커널 스레드
- 커널스레드의 개수는 응용 프로그램 혹은 특정 기계에 따라 결정된다.
- 사용자 스레드 만큼의 커널 스레드가 생성되지 않지만 병렬 실행은 가능하다.
- 구현하기가 어렵고 처리 코어 수가 증가함에 따라 커널 스레드 수 제한의 중요성이 줄어들었다.
프로그래머에게 스레드를 생성하고 관리하기 위한 API 를 제공한다.
- 커널의 지원 없이 사용자 공간에서 사용하는 라이브러리
- 라이브러리의 모든 코드, 자료구조가 사용자 공간에 존재하기에 시스템 콜이 아닌 사용자 공간의 지역 함수를 호출 하는 것
- 운영체제에 의해 지원되는 커널 수준 라이브러리
- 커널 시스템 콜을 부르는 것
스레딩의 생성과 관리 책임을 개발자로부터 컴파일러, 런타임 라이브러리에게 넘기는 것
프로세스를 시작할 때 일정한 수의 스레드를 미리 풀로 만들어두는 것을 뜻한다. 요청마다 스레드를 만든다면 언젠가 시스템 자원의 고갈될 수 있기에 스레드 풀을 사용하여 최대 스레드 수의 제한을 할 수 있다.
- 할일이 없는 스레드는 일을 기다리는 유휴 스레드로 대기한다.
- 요청을 받으면 유휴 스레드가 깨어나서 처리한다.
- 유휴 스레드가 없는 경우 유휴 스레드가 생길 때까지 작업을 대기한다.
- 스레드를 새로 만드는 것보다 기존 스레드로 처리하는 것이 더 빠를 수 있다.
- 스레드 풀의 스레드 개수는 코어의 수, 물리 메모리 용량, 요청 클라이언트의 최대 개수등을 고려하여 정해진다.
- 상황에 맞게 동적으로 풀의 크기를 변경할 수도 있다.
부모 스레드가 하나 이상의 자식 스레드를 생성(fork) 한 다음 자식의 종료를 기다린 후 join 한 뒤 자식의 결과를 확인하고 결합하는 방식이다.
- Java 의 Quicksort, Mergesort 와 같은 재귀 분할-정복 알고리즘과 함께 사용되도록 설계된 버전에서 사용된다.
- ForkJoinPool 의 각 스레드는 fork 된 작업의 큐를 유지 관리한다.
- 스레드의 작업 큐가 비어 있으면 work stealing 알고리즘을 사용해 다른 스레드 큐에서 작업을 가져옴으로써 스레드의 부하를 분산시킬 수 있다.
공유 메모리 환경에서 병렬 프로그래밍을 할 수 있게 도와준다. 병렬화를 위한 커맨드를 제공하고 개발자가 병렬화 수준을 선택할 수 있게 한다.
- 필요한 스레드 개수 지정
- 데이터를 스레드들이 공유할지, 특정 스레드만 사용할 것 인지
macOS, IOS 운영체제를 위해 애플에서 개발한 기술로 개발자가 병렬로 실행될 테스크를 식별할 수 있도록 하는 런타임 라이브러리, API 및 언어 확장의 조합.
- 테스크를 디스패치 큐에 넣어서 스케줄 한뒤 스레드 풀에서 가용 스레드를 선택하여 테스크를 할당하며 큐에서 테스크를 제거한다.
- 직렬 디스패치 큐
- FIFO 로 제거된다.
- 프로세스에는 고유한 직렬 큐가 있다.개발자는 특정 프로세스에 추가 직렬 큐를 만들 수 있다.
- 큐에서 제거된 테스크는 다른 테스크가 제거되기 전에 실행을 완료해야 하기에 여러 작업을 순차적으로 실행하는데 유용한다.
- 병행 디스패치 큐
- FIFO 로 제거된다.
- 한번에 여러 테스크가 제거되어 병렬로 실행될 수 있게 한다.
- 직렬 디스패치 큐
한 프로그램의 스레드가 fork() 를 호출시
- 모든 스레드를 복제해야 해서 새 프로세스를 만드는 경우
- 만약 fork 후에 exec 를 하지 않는다면 모든 스레드들을 복제해야 한다.
- 한개의 스레드만 가지는 새 프로세스를 만드는 경우
- 만약 fork 이후 exec 를 한다면 exec 에서 지정한 프로그램이 다시 대체할 것이기 때문에 모든 스레드를 복제할 필요가 없다.
UNIX 에서 신호는 프로세스에 어떤 이벤트가 일어났음을 알려주기 위해 사용되는데 신호는 특정 이벤트가 일어나야 생성되고 생성된 신호는 프로세스에게 전달된다. 전달받은 프로세스는 반드시 이에 대해서 처리해야 한다.
동기식 신호는 신호를 야기한 스레드에 전달되지만 비동기식 신호는 어느 스레드에 전달될지 명확하지 않기에 전달할 스레드를 선택해야 하며 여러 방식이 있다.
- 신호가 적용될 스레드에게 전달
- 모든 스레드에게 전달
- 몇몇 스레드들에만 선택적으로 전달
- 특정 스레드가 모든 신호를 전달받도록 지정
스레드가 끝나기 전에 강제 종료시키는 작업을 뜻하며 스레드들에 할당된 자원 문제 때문에 어려운 작업이다. 만약 스레드가 다른 스레드와 공유하는 자료구조를 갱신하는 도중에 취소 요청이 오는 경우 문제가 된다. 취소하려는 스레드를 목적 스레드라고 한다.
💡 스레드 취소 예시 : 여러 스레드가 DB를 병렬로 검색하다 한 스레드가 결과를 찾았다면 나머지는 취소되어도 된다.- 비동기식 취소 : 한 스레드가 즉시 목적 스레드를 강제 종료하는 것을 뜻한다.
- 취소된 스레드로부터 시스템 자원을 다 회수하지 못하는 경우가 발생하기에 사용할 수 없는 시스템 자원들이 생기며 최악의 경우 모든 자원이 사용 불가해 질수도 있다.
- 지연 취소 : 목적 스레드가 주기적으로 자신이 강제 종료되어야 하는지를 점검하며 목적 스레드들이 질서정연하게 강제 종료될 수 있다.
- 실제 취소는 목적 스레드가 취소 여부를 결정하기 위한 플래그를 검사한 이후 일어난다. 또한 스레드는 자신이 취소되어도 안전하다고 판단되는 시점에 취소 여부를 검사할 수 있다.
한 프로세스에 속한 스레드는 프로세스의 데이터를 공유하지만 상황에 따라서 각 스레드가 자기만 액세스할 수 있는 데이터를 가져야 하는데 이를 TLS 라고 한다. TLS 는 지역 변수와 다르게 전체 함수 호출에 걸쳐서 보인다.
💡 예시 : 트랜잭션 처리 시스템에서 고유한 식별자를 가진 트랜잭션을 독립된 스레드가 처리하게 하려 할 때- Java의 ThreadLocal
Many-to-Many 나 혼합 모델에서 스레드 라이브러리와 커널의 통신 문제를 반드시 해결해야 한다.
- 통신의 조정을 통해서 응용 프로그램이 최고의 성능을 보이게 커널 스레드의 수를 동적으로 조절이 가능하다.
- 해당 모델을 구현한 많은 시스템은 사용자와 커널 스레드 사이에 경량 프로세스 또는 LWP 라는 중간 자료구조를 둔다.
사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중 하나
- 커널은 응용 프로그램에 가상 처리기인 LWP 집합을 제공하며 사용자 스레드를 가용한 가상 처리기로 스케줄한다.
- 이 때 실제로 물리 처리기에서 스케줄 하는 대상은 커널 스레드이다.
- LWP 는 하나의 커널 스레드에 부속되어 있기에 커널 스레드가 봉쇄되면 LWP 도 같이 봉쇄되며 LWP 에 부속된 사용자 스레드도 같이 봉쇄된다.
- 만약 프로세스가 4개의 LWP 를 가지고 있을 때 5개의 파일 읽기 요청이 발생한다면 LWP 가 입출력 완료를 기다리면서 대기 하기 위해선 5개의 LWP 가 필요하기에 마지막 요청은 하나의 LWP 가 커널에서 복귀할 때까지 기다려야 한다.
- 커널은 응용 프로그램에게 특정 이벤트에 대해 알려주며 이 프로시저를 upcall 이라고 한다.
- 스레드 라이브러리의 Upcall 처러기에 의해 처리된다.
- Upcall 처리기는 LWP 상에서 실행되어야 한다.
- 스레드 라이브러리의 Upcall 처러기에 의해 처리된다.