Tiny Star

🪄Interview/✏️Study

[CS STUDY INTERVIEW] 7주차 - 스레드(Threads)

청크 2024. 4. 5. 11:28

CS 스터디 7주차 

 

스레드(Threads)


스레드(Threads)

컴퓨팅 분야에서 다양한 맥락에서 사용되지만, 일반적으로 프로세스 내에서 실행되는 작업의 최소 단위를 의미한다.

스레드는 프로세스 내에서 독립적으로 실행되는 흐름의 단위로, 각 스레드는 프로세스의 코드를 실행하며,

자신만의 실행 상태(예: 프로그램 카운터), 스택, 레지스터 집합을 가지고 있지만

같은 프로세스 내의 다른 스레드와 힙 메모리 영역, 데이터 세그먼트, 코드 세그먼트 등을 공유한다.

 

이는 스레드의 특징과도 겹치는 부분인데 스레드 특징을 살펴보면

 

1) 경량성(Lightweight)

프로세스에 비해서 생성이나 종료, 컨텍스트 스위칭이 빠르고 자원을 효율적으로 사용할 수 있다.

 

2) 자원공유(Resource Sharing)

프로세스 내 스레드들은 stack만 따로 할당 받고, code, data, heap 영역은 공유한다.

개별 스레드는 별도의 Register와 Stack을 갖고, Heap 메모리는 서로 읽고 쓰기가 가능하며

프로세스 내의 주소 공간이나 자원들(Heap 공간)을 같은 프로세스 내에 스레드끼리 공유하면서 실행되기 때문에 스레드 간 통신은 비교적 쉬운 편이다.

 

이러한 자원 공유로 데이터 공유 및 통신이 용이하지만 동기화 문제가 발생할 수 있다.

 

3) 효율성(Efficiency)

스레드는 멀티코어 프로세서의 병렬 처리 능력 즉, 다중 스레드 사용이 가능해서 동시 실행 시 병렬성과 응답성 향상에 좋다.

-> 병렬성 : 동시에 여러 작업을 처리하는 능력으로 작업을 동시에 처리함으로써 시스템의 성능을 향상

-> 응답성 : 사용자의 입력이나 외부 이벤트에 대한 빠른 반응을 의미하며 이벤트를 처리하는 동안 다른 작업이 지연되지 않고 실행되어야 함

또한 하나의 프로세스에 속한 스레드 간에는 컨텍스트 전환 시 비용이 적다는 장점이 있다.


멀티스레딩(Multithreading)

하나의 프로세스 안에 여러 개의 스레드가 존재하여 내부 자원을 공유하며 작업을 나누어 수행하는 것으로 

하나의 프로그램에서 두가지 이상의 동작을 동시에 처리가 가능하도록 해준다.

 

만약 멀티 프로세스가 웹 브라우저에서의 여러 탭이나 창의 기능이라면

멀티 스레드는 웹 브라우저 단일 탭 또는 창 내의 브라우저 이벤트 루프, 네트워크 처리, I/O 및 기타 작업을 관리하고 처리하는데 사용된다.

 

ex) 웹 서버 : 사용자가 서버 DB에 자료 요청을 하는 동안 브라우저의 다른 기능을 이용할 수 있도록 하나의 스레드가 지연되더라도

다른 스레드는 타 작업을 지속할 수 있게 됨

 

멀티스레딩의 장점

1) 자원의 효율성 증대

기본적으로 스레드는 프로세스보다 용량이 가볍기 때문에 시스템 자원 소모가 감소된다.

스레드는 프로세스 내에서 생성되기 때문에 기본적으로 내장되어 있는 데이터 용량이 작고 실행 환경 설정이

비교적 간단하여 생성 및 종료가 빠르다.

힙 영역은 보통 프로세스 내의 모든 스레드에 의해 공유되는 메모리 공간인데,

이 공간을 활용하여 스레드 간 자원 공유가 가능하기 때문에 IPC를 사용하지 않고도 데이터 공유가 가능하다.

 

2)Context Switching 비용 감소

스위칭할 때 스레드 간에 공유하는 자원을 제외한 스레드 정보(stack, register)만을 교체하면 되므로

프로세스 컨텍스트 스위칭 비용보다 상대적으로 낮은 편에 속하는 것이 장점이다.

 

멀티스레딩의 단점

1)안정성 문제

멀티 프로세스와 달리 멀티 스레드는 하나의 스레드에서 문제가 발생한다면 타 스레드도 영행을 받아 프로그램이 전체 종료될 수 있다.

(병목현상, 데드락 등)

스레드에 에러가 발생할 경우 이에 대한 적절한 예외 처리를 잘 해놓는다던지, 에러 발생 시 새로운 스레드를 생성하거나

스레드 풀(Thread Pool)에서 잔여 스레드를 가져오던지 하여 프로그램 종료를 방지할 수 있지만

이럴 경우 새로운 스레드 생성 또는 작업이 없는 스레드 처리에 추가 비용이 발생하게 된다.

 

2) 동기화로 인한 성능 저하

여러 개의 스레드가 공유 자원을 동시에 접근한 경우에 어느 한 곳에서 자원이 변경된다면,

접근한 스레드는 의도하지 않은 값을 읽어 서비스에 전달하여 치명적인 버그를 발생시킬 우려가 있다.

 

스레드 간 동기화 작업으로 여러 스레드들이 자원에 접근할 때 순차적으로 통제가 가능하여

동시 접근/동시 수정 현상을 발생하지 않지만 스레드 접근 제한으로 병목 현상이 일어나 성능저하 우려된다.

 

3)데드락 (교착 상태)

다수의 프로세스나 스레드가 서로 자원을 점유하고, 다른 프로세스나 스레드가 점유한 자원을 기다리는 상황에서

서로 대기하며 무한 루프에 빠지며 발생하는 교착상태에 빠질 수 있다.

 

4)디버깅

여러 개의 스레드가 동시에 실행되기 때문에 각 스레드의 동작 추적이 어려워 코드 디버깅이 어려워질 수 있지만

이는 스레드 간 상호작용 및 동기화 기법을 이해한다면 해결이 가능한 단점이라고 본다.


동기화(Synchronization)

동기화(Synchronization)는 여러 스레드 또는 프로세스가 동시에 실행될 때 발생할 수 있는 자원 공유와 관련된 문제를 해결하기 위한 개념이다.

멀티스레딩 환경에서는 여러 스레드가 같은 메모리 영역에 동시에 액세스하거나 변경할 수 있기 때문에,

이러한 동작이 제대로 조정되지 않으면 데이터 불일치, 경쟁 상태(race condition), 데드락(deadlock) 등의 문제가 발생할 수 있다.

따라서, 동기화 메커니즘은 이러한 문제를 방지하고 안전한 자원 공유를 가능하게 합니다.

 

주요 동기화 기법

1) 뮤텍스(Mutexes)

Mutual exclusion의 약자로, 어떤 자원을 한 번에 하나의 스레드만 사용할 수 있도록 보장하는 동기화 기법이다.

뮤텍스를 사용하면 해당 자원에 액세스하기 전에 뮤텍스를 획득(lock)하고,

자원 사용 후에 뮤텍스를 해제(unlock)하여 다른 스레드가 사용할 수 있도록 하여 문제는 방지한다.

 

2) 세마포어(Semaphores)

세마포어는 뮤텍스와 유사하지만, 동시에 자원에 접근할 수 있는 스레드의 수를 제한할 수 있어서

예를 들면, 세마포어를 2로 설정하면 동시에 최대 2개의 스레드만이 자원에 접근할 수 있도록 한다.

 

3) 모니터(Monitors)

모니터는 클래스 또는 객체에 대한 동기화를 캡슐화하는 데 사용되는 고수준 동기화 메커니즘으로,

모니터 내의 모든 메소드는 자동으로 뮤텍스를 사용하여 동기화되며, 한 번에 하나의 스레드만이 모니터의 메소드를 실행할 수 있다.

 

4) 조건 변수(Condition Variables)

스레드가 특정 조건이 충족될 때까지 대기하게 하거나, 조건이 충족될 때 스레드를 깨울 수 있는 동기화 메커니즘으로,

조건 변수는 일반적으로 뮤텍스와 함께 사용한다.

 

동기화의 중요성

동기화는 멀티스레딩 프로그램의 정확성과 신뢰성을 보장하는 데 필수적이라고 볼 수 있다.

올바른 동기화 없이는 프로그램이 예측 불가능한 결과를 초래할 수 있으며, 이는 디버깅하기 매우 어려운 버그로 이어질 수 있기 때문에

특히, 금융, 의료, 실시간 시스템 등의 중요한 분야에서는 데이터의 정확성과 일관성이 매우 중요하여 효과적인 동기화 전략이 필수적이다.

 

동기화 사용 시 고려사항

동기화를 사용할 때는 데드락, 라이브락(livelock), 스타베이션(starvation)과 같은 상황을 피하기 위한 주의가 항상 필요하다.

데드락은 여러 스레드가 서로 다른 자원의 잠금을 기다리면서 무한히 대기하는 상황을 말하고,

라이브락은 스레드가 실행되고 있지만 진행이 없는 상태,

스타베이션은 하나 또는 여러 스레드가 자원을 기다리면서 영원히 실행되지 못하는 상황을 말한다.

따라서, 동기화는 필요한 최소한으로 사용하고, 가능하다면 동기화가 필요 없는 설계를 고려하는 것이 좋겠다.

 


스레드 풀(Thread Pool)

스레드 생성과 종료에 따른 오버헤드를 줄이기 위해 미리 생성된 스레드들의 집합을 관리하는 방식으로 요청에 따라 재사용 가능한 스레드를 할당한다.

 

조금 더 자세히 설명해보자면

사전에 정해진 수의 스레드를 생성하여 풀(pool)에 보관하고, 이 스레드들을 작업에 재사용하는 기법으로

이 방식은 스레드를 필요할 때마다 매번 생성하고 소멸시키는 것보다 효율적이라고 할 수 있다.

스레드의 생성과 종료는 비용이 많이 드는 작업이므로, 스레드 풀을 사용하면 시스템의 성능을 향상시킬 수 있고,

주로 서버와 같이 동시에 많은 요청을 처리해야 하는 환경에서 유용하게 사용된다.

 

스레드 풀의 주요 특징

1) 자원의 재사용

이미 생성된 스레드를 작업에 재사용함으로써, 스레드 생성 및 종료에 따른 오버헤드를 줄일 수 있다.

2) 작업 처리량 증가

스레드 풀을 사용하면 시스템 리소스를 더 효율적으로 사용하여 더 많은 작업을 빠르게 처리할 수 있다.

3) 자원의 한계 관리

스레드 풀의 크기를 제한함으로써, 시스템에 과도한 부하가 걸리는 것을 방지할 수 있으며 이는 시스템의 안정성을 유지하는 데 도움이 된다.

 

스레드 풀의 작동 방식

1) 스레드 풀 초기화

애플리케이션이 시작할 때, 사전에 정의된 수의 스레드가 스레드 풀에 생성되고 대기 상태가 된다.

2) 작업 요청 처리

애플리케이션 또는 사용자로부터 작업 요청이 들어오면, 스레드 풀에서 대기 중인 스레드 중 하나가 작업을 할당받아 실행한다.

3) 작업 실행

할당받은 스레드는 작업을 처리하고, 처리가 완료되면 결과를 반환한다.

4) 스레드 재사용

작업을 마친 스레드는 다시 스레드 풀로 돌아가 다음 작업을 기다린다.

 

스레드 풀 사용 시 고려사항

1) 스레드 풀 크기

너무 적은 수의 스레드는 시스템 리소스를 충분히 활용하지 못할 수 있고,

너무 많은 수의 스레드는 컨텍스트 스위칭으로 인한 오버헤드가 증가할 수 있기 때문에

스레드 풀의 크기는 시스템의 하드웨어 자원, 예상되는 작업량, 작업의 종류 등 여러 요소를 고려하여 적절히 설정해야 한다.

 

2) 작업 큐

스레드 풀에서 사용 가능한 스레드가 없을 때, 새로운 작업은 이 큐에 추가되어 대기하기 때문에 대기 중인 작업을 관리하기 위해 작업 큐를 사용한다.

 

3) 스레드 풀 정책

작업 거부 정책(Rejection Policy), 작업 큐의 크기, 스레드 생성 및 소멸 정책 등 스레드 풀의 동작을 결정하는 다양한 정책을 설정이 가능하다.

 

스레드 풀은 Java의 ExecutorService, .NET의 ThreadPool, Python의 concurrent.futures.ThreadPoolExecutor 등

다양한 프로그래밍 언어와 라이브러리에서 지원되고 있으며

이러한 구현체들은 스레드 풀의 생성, 관리, 작업 할당 및 실행 등을 쉽게 처리할 수 있도록 도와준다.


작동 방식

스레드의 작동 방식을 이해하려면 우선 스레드가 컴퓨터 시스템 내에서 어떻게 실행되는지,

운영 체제가 스레드를 어떻게 관리하는지에 대한 기본적인 지식이 필요하다.

스레드는 프로세스 내에서 실행되는 실행 단위로, 프로세스의 자원을 공유하면서 독립적인 실행 흐름을 가지는데

여기서는 스레드의 생성부터 종료까지의 일반적인 작동 방식에 대해 설명해보겠다.

 

생성

프로그램이나 프로세스가 시작할 때 또는 명시적으로 스레드를 생성하는 API 호출을 통해 스레드가 생성되는 단계

실행

스레드는 운영 체제 스케줄러에 의해 관리되며, CPU 코어에 할당되어 실행되는 단계로,

멀티코어 시스템에서는 여러 스레드가 동시에 실행된다.

 

컨텍스트 스위칭

실행 중인 스레드가 대기 상태로 전환되고, 다른 스레드가 CPU를 할당받아 실행되는 과정으로 이는 운영 체제의 스케줄러에 의해 관리된다.

 

종료

스레드는 실행이 완료되거나, 명시적으로 종료 함수를 호출하여 종료한다.

 

스레드의 동작 방식과 관리는 사용되는 운영 체제와 프로그래밍 언어의 API에 따라 다소 차이가 있을 수 있는데,

예를 들어, Java에서는 Thread 클래스를 상속받거나 Runnable 인터페이스를 구현하여 스레드를 사용할 수 있지만

C언어에서는 POSIX 스레드(흔히 pthreads라 불림) 라이브러리를 사용하여 스레드를 생성하고 관리가 가능하다.

 

스레드의 생성

1) 스레드 생성

프로그램 내에서 스레드 생성 API를 호출하여 새로운 스레드를 생성한다.

이 때, 실행할 작업(함수 또는 메서드)을 지정하는데 예를 들어, Java에서는 Thread 클래스를 이용하거나,

Runnable 인터페이스를 구현하는 방식으로 스레드를 생성할 수 있다.

 

2) 스레드 초기화

스레드 생성 시, 운영 체제는 스레드에 필요한 자원을 할당하고 스레드를 초기화하는 과정으로

이 과정에서 스레드는 자신만의 스택 공간을 할당받으며, 프로세스 내의 다른 스레드와 코드, 데이터, 힙 영역 등을 공유하게 된다.

 

스레드의 실행

1) 스레드 스케줄링

운영 체제의 스케줄러는 준비 상태에 있는 스레드 중 하나를 선택하여 프로세서(CPU)에 할당한다.

스레드는 운영 체제의 스케줄링 정책에 따라 실행 순서가 결정되며

이 때, 스레드의 우선순위, 라운드-로빈 방식, 우선순위에 따른 시분할 방식 등 다양한 스케줄링 알고리즘이 사용될 수 있다.

 

2) 스레드 실행

프로세서에 할당된 스레드는 실행 상태가 되며, 지정된 작업(코드)을 실행하는데

스레드는 필요한 계산을 수행하고, 필요에 따라 다른 자원(예: 파일, 네트워크 소켓)에 접근할 수 있다.

 

스레드의 상태 전환

1) 대기(Waiting)/블로킹(Blocking)

스레드가 I/O 작업, 데이터의 동기화, 특정 조건의 충족 등을 기다리는 동안, 스레드는 실행을 일시 중지하고 대기 상태로 전환된다.

 

2) 컨텍스트 스위칭(Context Switching)

현재 실행 중인 스레드가 대기 상태로 전환되고, 다른 스레드가 프로세서를 할당받아 실행 상태로 전환될 때 발생하며

이 과정에서 스레드의 상태, 프로그램 카운터, 레지스터 값 등의 컨텍스트 정보가 저장되고 복원된다.

 

스레드의 종료

1)자발적 종료

스레드가 할당된 작업을 완료하면, 스레드는 종료되어 시스템에 의해 정리된다.

 

2) 강제 종료

예외 발생 또는 외부 명령에 의해 스레드가 강제로 종료될 수 있습니다. 이 경우, 스레드는 정리 과정을 거치지 않고 즉시 종료될 수 있다.

 

팀장님에게 들은 바에 의하면 과거에는 컴퓨터의 CPU나 코어 수가 굉장히 적었기 때문에 스레드를 사용했지만,

하드웨어가 고도로 발달된 요즘 세상에서 멀티 프로세싱이 있기때문에 스레드는 가장 쓸모없는 기능 중 하나라고 한다.


면접 예상 질문과 답변

Q1. 스레드란?
A1. 일반적으로 프로세스 내에서 실행되는 작업의 최소 단위를 의미합니다.

 

Q2. 멀티스레딩이란 무엇이며, 왜 사용하는가?
A2. 멀티스레딩은 하나의 프로세스 내에서 여러 스레드를 생성해 동시에 여러 작업을 수행하는 기법입니다.

이를 통해 자원의 효율적 사용, 성능 향상, 응답성 개선 등의 이점을 얻을 수 있습니다.

 

Q3. 스레드의 동기화는 왜 중요한가?
A3. 멀티스레딩 환경에서 여러 스레드가 동일한 메모리 영역(자원)에 동시에 액세스할 때

데이터 불일치, 경쟁 상태, 데드락 등의 문제가 발생할 수 있습니다.

동기화는 이러한 문제를 방지하고 안전한 자원 공유를 보장하기 위해 필수적입니다.

 

Q4. 뮤텍스와 세마포어의 차이점
A4. 뮤텍스는 한 번에 하나의 스레드만이 특정 자원에 접근할 수 있도록 하는 동기화 메커니즘입니다.

반면, 세마포어는 동시에 접근할 수 있는 스레드의 수를 제한할 수 있어, 예를 들어, 동시에 2개의 스레드가 접근 가능하도록 할 수 있습니다.

 

Q5. 스레드 풀이란 무엇이며, 어떤 장점이 있는가?
A5. 스레드 풀은 사전에 정의된 수의 스레드를 미리 생성하여 풀에 보관하고, 필요할 때마다 재사용하는 기법입니다.

이는 스레드 생성 및 종료에 따른 오버헤드를 줄이고, 시스템 리소스의 효율적 사용을 가능하게 합니다.

 

Q6. 컨텍스트 스위칭이란 무엇이며, 멀티스레딩에서 어떤 영향을 미치는가?
A6. 컨텍스트 스위칭은 현재 실행 중인 태스크(프로세스 또는 스레드)의 상태를 저장하고 다른 태스크로 CPU의 제어를 이전하는 과정입니다.

멀티스레딩에서 스레드 간 빈번한 컨텍스트 스위칭은 오버헤드를 발생시켜 성능 저하를 일으킬 수 있습니다.

 

Q7. 스레드의 생명 주기는 어떻게 되는가?
A7. 스레드의 생명 주기에는 생성, 실행(실행 대기), 실행, 블록(대기), 종료 상태가 있습니다.

스레드는 생성된 후 실행 대기 상태로 이동하며, 스케줄러에 의해 실행 상태로 전환됩니다.

실행 중인 스레드는 대기 상태로 전환될 수 있고, 작업 완료 후에는 종료됩니다.

 

Q8. 데드락이란 무엇이며, 어떻게 예방할 수 있나?
A8. 데드락은 두 개 이상의 스레드가 서로의 자원을 기다리면서 무한 대기에 빠지는 상태를 의미합니다.

데드락을 예방하기 위해 자원 할당 순서 정하기, 자원 요청 시 다른 자원을 모두 해제 후 재요청하기, 자원에 대한 타임아웃 설정 등의 전략을 사용할 수 있습니다.

 

Q9. 멀티스레딩 환경에서 스레드의 안전한 종료 방법은 무엇인가?
A9. 스레드를 안전하게 종료하기 위해 외부에서 제어 신호를 보내어 스레드가 현재 작업을 마무리하고 종료하도록 유도하는 방법이 있습니다.

예를 들어, 스레드 내부에서 주기적으로 종료 신호를 확인하고, 신호가 설정되면 정리 작업 후 스레드를 종료합니다.

 

Q10. 스레드와 멀티프로세싱의 차이점은 무엇이며, 언제 각각을 사용하는 것이 좋은가?
A10. 스레드는 가벼운 실행 단위로 자원을 공유하여 효율적인 실행이 가능하지만, 공유 자원으로 인한 복잡성과 안정성 문제가 있을 수 있습니다.

멀티프로세싱은 프로세스 간 독립적인 메모리 공간을 사용하여 안정성이 높지만, 자원 공유와 통신 비용이 더 큽니다.

고성능 병렬 처리가 필요할 때는 멀티프로세싱을, 자원의 효율적 사용과 응답성 개선이 중요할 때는 멀티스레딩을 사용하는 것이 좋습니다.