1. 프로세스와 스레드
프로세스는 운영 체제에서 실행 중인 하나의 독립된 작업 단위로, 자체 메모리 공간을 가지며 CPU 자원을 할당받아 실행됩니다. 각 프로세스는 독립적인 메모리 공간을 할당받기 때문에 다른 프로세스와 메모리를 공유하지 않습니다.
스레드는 프로세스 내에서 실행되는 작은 실행 단위입니다. 하나의 프로세스 안에는 여러 스레드가 있을 수 있으며, 이들은 같은 메모리 공간(주로 힙 영역)을 공유합니다. 스레드는 같은 프로세스 내에서 메모리를 공유하며, 메모리 사용에 있어서 효율적입니다.
2. 메모리 영역
프로그램이 실행될 때 사용되는 주요 메모리 영역은 다음과 같습니다.
- 코드(Code) 영역: 프로그램 코드가 저장되는 영역으로, 함수와 메서드의 명령어가 저장됩니다. 보통 읽기 전용으로, 실행되는 동안 변하지 않습니다.
- 데이터(Data) 영역: 전역 변수나 정적 변수들이 저장되는 영역입니다. 프로그램 실행 시작부터 종료 시까지 유지됩니다.
- 힙(Heap) 영역: 동적 메모리 할당 시 사용되는 영역으로, 실행 중에 자유롭게 메모리를 할당하고 해제할 수 있습니다. 스레드 간에 공유될 수 있습니다.
- 스택(Stack) 영역: 함수 호출 시 지역 변수나 매개변수가 저장되는 영역으로, 함수가 종료되면 자동으로 메모리가 해제됩니다. 스레드마다 독립된 스택을 가지고 있어 서로 격리됩니다.
3. 컨텍스트 스위칭
컨텍스트 스위칭은 CPU가 현재 실행 중인 프로세스 또는 스레드의 상태를 저장하고 다른 프로세스 또는 스레드로 전환하는 과정입니다. 이 과정에서 필요한 작업은 다음과 같습니다.
과정
1. 현재 실행 중인 프로세스의 레지스터 상태와 프로그램 카운터를 저장합니다.
2. 다음에 실행할 프로세스 또는 스레드의 레지스터 값과 프로그램 카운터를 복원합니다.
3. 스케줄러가 다음에 실행할 프로세스를 결정합니다.컨텍스트 스위칭은 시간 소모가 발생하기 때문에 효율적인 스케줄링이 중요합니다.
4. 레이스 컨디션
레이스 컨디션은 여러 스레드가 공유 자원에 동시에 접근하여 발생하는 문제입니다. 예를 들어, 두 스레드가 같은 변수에 값을 변경할 때, 순서에 따라 예기치 않은 결과가 발생할 수 있습니다. 이를 방지하기 위해 동기화 기법을 사용합니다.
5. 세마포어와 뮤택스
- 세마포어(Semaphore): 특정 자원을 동시에 접근할 수 있는 최대 스레드 수를 정할 수 있는 동기화 객체입니다. 세마포어는 여러 스레드가 접근할 수 있는 자원 수를 정해 동시 접근을 조절합니다.
- 뮤택스(Mutex): 하나의 스레드만 자원에 접근할 수 있도록 하는 동기화 객체로, 공유 자원의 상호 배제를 위해 사용됩니다. 뮤택스를 사용하면 특정 시점에 하나의 스레드만 자원에 접근할 수 있습니다.
6. 데드락과 해결 방법
데드락(Deadlock)은 두 개 이상의 스레드가 서로 자원을 기다리며 무한히 대기하는 상태를 말합니다. 데드락은 프로세스나 스레드가 자원을 할당받은 후 다른 자원을 기다리면서 발생합니다.
해결 방법
- 상호 배제 피하기: 한 자원을 여러 스레드가 공유하지 않도록 합니다.
- 순환 대기 방지: 자원에 순서를 지정하고, 특정 순서로 자원을 할당합니다.
- 점유 대기 방지: 자원이 사용 중일 때 다른 자원을 대기하는 상황을 방지합니다.
- 기아 방지: 특정 스레드가 자원을 오랜 시간 기다리는 상황을 방지하기 위한 정책을 둡니다.
7. 스레드 풀(Thread Pool)
스레드 풀은 미리 생성된 스레드들의 그룹으로, 특정 수의 스레드를 미리 만들어 놓고 요청이 발생할 때마다 스레드를 할당합니다. 이렇게 하면 스레드를 새로 생성하고 제거하는 비용을 줄일 수 있어 성능 최적화에 유리합니다. 서버나 고성능 애플리케이션에서 자주 사용됩니다.
8. 스레드 안전성(Thread Safety)
스레드 안전성은 여러 스레드가 동시에 같은 자원에 접근하더라도 의도된 결과가 보장되는 상태를 말합니다. 스레드 안전성을 보장하기 위해서는 동기화 기법이 중요하며, 이를 위해 뮤택스, 세마포어 등을 활용합니다. 또한, 언어와 라이브러리에서 제공하는 스레드 안전한 자료구조를 사용하는 것도 좋은 방법입니다.
9. 원자성(Atomicity)
원자성은 연산이 더 이상 쪼개지지 않고 중간에 다른 작업이 끼어들 수 없는 특성을 말합니다. 예를 들어, x++는 실제로 읽기, 증가, 쓰기라는 세 가지 단계로 이루어져 있기 때문에 원자적이지 않으나, 원자적 연산을 통해 이를 보장할 수 있습니다. 일부 언어와 라이브러리는 원자적 연산 함수를 제공하여 원자성을 쉽게 확보할 수 있게 해줍니다.
10. 조건 변수(Condition Variable)
조건 변수는 특정 조건이 충족될 때까지 스레드를 대기시키고, 조건이 만족되면 대기 중인 스레드를 깨워주는 동기화 메커니즘입니다. 이를 통해 여러 스레드 간에 더 복잡한 상호작용이 가능해집니다. 예를 들어, 생산자-소비자 문제에서 조건 변수를 통해 생산자가 자원을 생산하거나 소비자가 자원을 소비할 때 올바르게 동기화할 수 있습니다.
11. 리더-팔로워 패턴(Leader-Follower Pattern)
이 패턴은 동시성을 효율적으로 관리하는 디자인 패턴으로, 서버와 같은 멀티스레드 환경에서 자주 사용됩니다. 하나의 리더 스레드가 특정 작업을 처리하고 나면 다음 스레드가 리더 역할을 맡고, 기존 리더는 팔로워로 전환되어 대기하는 방식입니다. 이 패턴은 자원 활용을 최적화하고 컨텍스트 스위칭을 줄이는 데 도움을 줍니다.
12. 잠금 경합(Lock Contention)과 해결
잠금 경합은 여러 스레드가 동일한 잠금(lock)을 필요로 하면서 자주 발생하는 문제입니다. 이러한 경합은 성능 저하를 일으킬 수 있으므로, 잠금의 사용을 최소화하거나, 락 프리(lock-free) 알고리즘을 사용하는 방법으로 해결할 수 있습니다. 락 프리 알고리즘은 잠금 없이도 원자적 연산을 보장할 수 있는 방식으로, 성능 개선에 큰 도움이 됩니다.
락 프리(lock-free) 알고리즘
락 프리 알고리즘은 공유 자원에 접근할 때 잠금을 사용하지 않고도 데이터 무결성을 보장하는 방법입니다. 락을 사용하지 않기 때문에 스레드가 블로킹되지 않으며, 성능과 응답성이 매우 중요할 때 유리합니다. 대신, CAS(Compare-And-Swap) 연산과 같은 원자적 연산을 활용해 동기화 문제를 해결합니다.
CAS(비교 및 교환) 연산은 메모리의 특정 값이 예상한 값일 때만 새로운 값으로 교체하는 연산입니다. 이 연산을 통해 여러 스레드가 동시에 값을 변경하려고 할 때 무효한 연산을 피할 수 있으며, 자원을 안전하게 관리할 수 있습니다.
장점: 락을 사용하지 않으므로 경합이 줄어들고, 스레드가 블로킹 상태에 빠지지 않습니다. 성능이 중요한 실시간 시스템에서 자주 사용됩니다.
단점: CAS 기반 알고리즘의 설계가 복잡할 수 있으며, 일부 플랫폼에서 CAS 연산이 지원되지 않을 수 있습니다.
이 개념들은 멀티쓰레딩 시스템을 깊이 있게 이해하는 데 도움을 주며, 실제 시스템에서 병목 현상을 줄이거나 성능을 최적화할 때 유용하게 활용할 수 있습니다.