스레드
스레드는 프로세스 내에서 작업을 수행할 때 사용되는 작업 단위이다. 하나의 응용 프로그램이 여러 프로세스를 가질 수 있고, 하나의 프로세스는 여러 스레드를 가질 수 있다. 병렬적으로 작업을 수행한다는 것이 프로세스와도 일맥상통해서 경량 프로세스(Light Weight Process)라고도 부른다. 스레드의 기능을 쓰지 않겠다고 하더라도, 프로세스에서는 이미 스레드가 하나씩은 동작하고 있다. 이렇게 스레드 하나만으로 동작하는 프로세스를 단일 스레드 프로세스(Single Threded Process)라고 한다.
기본적으로 프로세스끼리는 다른 프로세스의 데이터로 접근할 수가 없다. IPC로나마 간접적으로 프로세스의 데이터에 접근할 수 있는데, 스레드는 프로세스에 귀속되므로 IPC 같은 거 없이 프로세스의 데이터를 공유해서 사용할 수 있다.
스레드는 개인 저장소가 없나요?
있지만 매우 제한적이다. 각 스레드가 각자만의 스택 공간을 가지고 있는데, 스택 공간은 함수 내의 지역 변수를 저장할 때나 쓰는 곳이라는 것을 기억하자. 단일 스레드의 경우 프로세스의 스택 공간 전부를 혼자 쓴다고 보면 된다.
멀티 프로세싱과 멀티 스레딩
OS가 가진 프로세스 스케줄러에 의해 프로세스의 작업량은 적절히 쪼개져서 CPU에 의해 연산된다. CPU 코어가 많다면, 프로세스는 이 코어 저 코어 옮겨다니며 자신의 일을 어떻게든 수행시킨다.
프로세스가 OS에 귀속된다면, 스레드는 프로세스에 귀속된다. 그렇기에 스레드가 여러 개 있을 경우, 프로세스는 자신이 받은 작업량을 각 스레드에게 나눠주게 된다(실제와는 조금 다르다). 즉, CPU의 연산량은 사실 프로세스가 가지는 게 아니라 프로세스의 스레드가 할당받아서 처리하는 것이라고 할 수 있다.
이걸 왜 쓰는가
그러게... 가 아니라! 스레드는 분명 프로세스보다 나은 점이 많이 있다.
1. 프로세스보다 응답성이 빠르다
어렵게 프로세스를 여럿 실행할 거 없이, 하나의 프로세스에서도 A 스레드에서는 작업을 계속 수행하면서, B 스레드로 사용자와의 상호작용을 이어나갈 수 있다.
2. 자원 공유가 간단하다
IPC 기법을 쓸 필요가 없이, 프로세스 내의 자료에 직접적으로 접근 가능하다.
물론 문제도 적잖이 있긴 하다...
1. 스레드들 중에 하나만 고장나도, 프로세스 전체가 영향을 받는다
멀티 프로세스 환경에서는 프로세스 간 연결이 강하지 않다. 간접적으로만 통신할 수 있기 때문에 하나가 고장나도 다른 프로세스는 영향이 없다.
하지만 멀티 스레드 환경에서는 모든 스레드가 같은 자원을 쓰기에(=연결성이 강하기에), 하나가 고장나면 다른 스레드들까지 영향을 받게 된다.
2. 컨텍스트 스위칭이 많이 일어난다
리눅스OS는 스레드와 프로세스가 같은 방식으로 동작한다. 프로세스를 분할해서 작업하는 것도 컨텍스트 스위칭이 발목을 잡는데, 더 잘개 쪼개서 작업하는 스레드라면? 더 이상의 자세한 설명은 생략한다.
3. 동기화에 신경써야 한다
1번과 어느 정도 연관성이 있고, 가장 중요한 문제다. 모두가 같은 자원을 '동시에' 쓰기 때문에, A 스레드에서 G 변수를 저장한 다음 B 스레드가 변수를 읽어야 하는 것을, A 스레드에서 미처 일을 끝마치지 못했는데 B 스레드가 읽어버리는 사고가 쉽게 발생한다. 이렇게 되면 작업이 끝났을때 G 변수는 10으로 저장되지만, B 스레드는 저장되기 전의 값을 읽어 프로그램이 이상해져버린다. 숫자놀음이라 별 거 아닌 거 같지만, 실제로 돈이 오가는 곳에서 이런 문제가 발생한다면 그 때부터는 웃을 수 없게 된다. 나는 분명 입금을 했는데, 입금 처리가 되기 전에 자동출금으로 돈이 빠져나가고, 이렇게 입금은 반영되지 않으면서 출금만 되어버린 텅 빈 잔고가 된다면...?
위 세 문제 가운데에서도 가장 중요한 문제는 동기화 문제다. 스레드의 실행 순서를 개발자가 맘대로 정할 수가 없다보니, 정말 예측이 불가능하기 때문이다. 그렇기에 이런 문제를 최대한 회피하기 위해 여러가지 해결 방안이 나왔다.
상호 배제(Mutual Exclusion, Mutex)
상호 배제는 스레드 간의 간섭이 발생하지 않도록, 일종의 영역을 설정해서 한 스레드가 해당 영역을 나올 때까지 다른 스레드가 그곳에 들어갈 수 없도록 하는 것이다. 스레드가 화장실에 들어가서 볼 일을 볼 때까지 문을 잠그고 있다고 생각하면 편하다. 여기서 화장실을 담당하는 부분을 임계 영역(Critical Section), 문을 잠그는 부분을 말 그대로 잠금(Lock)이라고 한다.
대표적인 상호 배제 기법으로는 Mutex와 세마포어(Semaphore)가 있다.
세마포어
임계 영역에 여러 스레드가 들어갈 수 있게 하는 것을 뜻한다. 다만 정말로 모든 스레드가 들어올 수 있는 것은 아니고, 최대 스레드 수를 설정해서 일정 수 이상의 스레드는 접근할 수 없도록 막는다. 놀이기구 대기열을 생각하면 된다.
정원 수가 하나인 세마포어를 이진 세마포어(Binary Semaphore), 여러 개인 세마포어를 계수세마포어(Counting Semaphore)라고 한다.
뮤텍스 (Mutex)
임계 영역에 하나의 스레드만 들어갈 수 있게 하는 것을 뜻한다. 위의 상호 배제와 이름이 같다. 위에 설명한대로 스레드가 임계 영역에 들어가기 위해 Key를 얻고, 임계 영역에 들어가 Lock을 한 다음, 사용을 마치고 나가면서 Key 돌려놓는(Release)다. 임계 영역을 하나의 스레드가 '소유'한다는 것이 중점이다.
정원 수를 1로 설정한 세마포어나 다를 게 없어서, 뮤텍스를 이진 세마포어(Binary Semaphore)라고 할 것 같지만? 동작 방식, 사용 용도가 다르기에 둘은 구분된다.
데드락(교착 상태, DeadLock)
이렇게 보면 상호 배제는 신이고 나는 무적인 것 같지만, 이렇게 스레드가 다른 스레드의 접근을 제한할 수 있다는 것은, 자기 자신도 접근을 제한당할 수 있다는 것을 뜻한다. 만약 A와 B가 각자의 임계 영역에서 상대방의 임계 영역에 가려고 한다면 어떻게 될까? 서로가 각자의 문을 걸어잠그고 남의 집 문이 열리기만 기다리고 있으니(격겜에서 서로 니가와를 시전하고 있다고 생각해보자), 두 스레드 모두 작업이 진행되지 않는다. 이렇게 답답해지는 상태를 데드락이라고 한다. 무슨 레슬링 기술 이름 같다
기아 상태(Starvation)
데드락 외에도 스레드가 실행되지 않는 경우가 있다. 프로세스를 우선 순위 기반 스케줄링했을 경우에 발생하는 문제로, 우선 순위가 낮은 프로세스가 무한히 기다리게 되는 것을 들었다. 스레드에도 똑같은 경우가 발생할수가 있다.
'프로그래밍 > 개념원리' 카테고리의 다른 글
네트워크 (0) | 2024.04.21 |
---|---|
가상 메모리(임시) (0) | 2024.03.24 |
프로세스(임시) (0) | 2024.03.23 |
커널(임시) (0) | 2024.03.23 |
레지스터, 파이프라인(임시) (0) | 2024.03.23 |