Condition Synchronization(조건 동기화)
- 조건 동기화(Condition Synchronization)는 병행 프로그래밍에서 여러 스레드 또는 프로세스 간의 실행 순서를 조정하고 조율하는 메커니즘이다.
이는 특정 조건이 만족될 때까지 스레드나 프로세스의 실행을 블록하고, 조건이 충족되면 실행을 재개하는 방식으로 동작한다.
조건 변수(Condition Variable):
- 조건 동기화를 위해 사용되는 핵심적인 데이터 구조(waiting queue)이다.
- 스레드나 프로세스가 특정 조건을 기다리거나 조건의 변화를 알리는 데 사용된다.
- 조건 변수는 보통 뮤텍스(Mutex)와 함께 사용되어 조건의 평가와 스레드의 블록/언블록을 안전하게 관리한다.
Wait 연산:
- 스레드나 프로세스가 특정 조건을 기다리는 연산이다.
- 조건이 만족되지 않으면 해당 스레드는 블록(Block) 상태로 전환되어 실행이 중단된다.
- 이때 뮤텍스를 해제하여 다른 스레드가 조건을 변경할 수 있도록 한다.
Signal 연산:
- 조건이 충족되었음을 알리는 연산이다.
- 조건을 만족시키는 스레드가 Signal 연산을 호출하면, 해당 조건을 기다리는 스레드 중 하나가 블록 상태에서 해제되어 실행을 재개한다.
- 여러 개의 스레드가 기다리고 있다면, 시스템에 따라 무작위로 또는 우선순위에 따라 하나의 스레드가 선택된다.
Broadcast 연산:
- Signal 연산과 유사하지만, 조건을 기다리는 모든 스레드를 블록 상태에서 해제하는 연산이다.
- 조건을 만족시키는 스레드가 Broadcast 연산을 호출하면, 해당 조건을 기다리는 모든 스레드가 실행을 재개한다.
상태 변수(State Variable)
- 조건 동기화에서 공유 자원의 상태를 나타내는 변수이다.
- 상태 변수는 스레드 간에 공유되며, 스레드들은 이 변수의 값을 기반으로 조건을 평가하고 실행을 조정한다.
상태 변수의 정의:
- 상태 변수는 공유 자원의 현재 상태를 표현하는 데 사용되는 변수이다.
- 상태 변수의 값은 공유 자원의 가용성, 점유 여부, 데이터의 존재 여부 등을 나타낸다.
- 스레드들은 상태 변수의 값을 검사하여 특정 조건을 판단하고 실행을 결정한다.
상태 변수의 예시:
- 버퍼의 현재 크기를 나타내는 변수
- 공유 자원의 사용 가능 여부를 나타내는 boolean 변수
- 데이터의 존재 여부를 나타내는 변수
- 스레드의 실행 단계를 나타내는 변수
상태 변수와 조건 변수의 관계:
- 상태 변수는 조건 변수와 함께 사용되어 조건 동기화를 구현한다.
- 스레드는 조건 변수를 사용하여 상태 변수의 값을 기반으로 대기하거나 실행을 재개한다.
- 상태 변수의 값이 변경되면 조건 변수를 통해 대기 중인 스레드에게 신호를 보낸다.
상태 변수의 접근과 갱신:
- 상태 변수는 임계 영역(Critical Section)에서 접근되고 갱신된다.
- 스레드는 상호 배제(Mutual Exclusion) 메커니즘을 사용하여 상태 변수에 대한 배타적 접근을 보장한다.
- 상태 변수의 값을 변경할 때는 원자적(Atomic)으로 이루어져야 한다.
상태 변수의 초기화:
- 상태 변수는 공유 자원의 초기 상태를 나타내도록 초기화된다.
- 초기값은 공유 자원의 가용성, 데이터의 존재 여부 등을 반영한다.
상태 변수의 사용 예시:
- 생산자-소비자 문제에서 버퍼의 현재 크기를 나타내는 변수
- 읽기-쓰기 락(Lock)에서 공유 자원의 사용 가능 여부를 나타내는 변수
- 세마포어(Semaphore)에서 자원의 가용성을 나타내는 변수
- 상태 변수는 조건 동기화에서 핵심적인 역할을 한다.
- 스레드들은 상태 변수의 값을 기반으로 조건을 평가하고 실행을 조정하여 공유 자원에 대한 안전한 접근과 동기화를 보장한다.
상태 변수는 조건 변수와 함께 사용되어 스레드 간의 통신과 협력을 가능하게 한다.
- 상태 변수를 올바르게 정의하고 관리하는 것은 병행 프로그래밍에서 중요하다.
- 상태 변수의 초기화, 접근, 갱신은 임계 영역 내에서 이루어져야 하며, 스레드 간의 동기화와 상호 배제를 적절히 처리해야 한다.
- 상태 변수를 효과적으로 활용함으로써 병행 시스템의 정확성과 효율성을 높일 수 있다.
Simple join() 함수 구현 예시
- State variable :
done
- Lock for the state variable :
m
Producer/Consumer Problem
- 생산자-소비자 문제(Producer-Consumer Problem)는 병행 프로그래밍에서 자주 등장하는 고전적인 동기화 문제 중 하나이다.
- 이 문제는 두 가지 유형의 프로세스 또는 스레드, 즉 생산자(Producer)와 소비자(Consumer)가 공유 버퍼를 사용하여 데이터를 주고받는 상황을 다룬다.
문제 정의:
- 생산자(Producer)는 데이터를 생성하여 공유 버퍼에 저장하는 역할을 한다.
- 소비자(Consumer)는 공유 버퍼에서 데이터를 읽어와 소비하는 역할을 한다.
- 공유 버퍼는 한정된 크기를 가지며, 버퍼가 가득 차면 생산자는 대기해야 하고, 버퍼가 비어있으면 소비자는 대기해야 한다.
- 생산자와 소비자는 동시에 공유 버퍼에 접근할 수 있어 동기화 문제가 발생할 수 있다.
문제의 조건:
- 생산자는 공유 버퍼에 데이터를 추가할 수 있어야 한다.
- 소비자는 공유 버퍼에서 데이터를 읽어올 수 있어야 한다.
- 공유 버퍼의 크기는 제한되어 있다.
- 생산자는 버퍼가 가득 찼을 때 대기해야 하고, 소비자는 버퍼가 비어있을 때 대기해야 한다.
- 데이터의 무결성을 보장하기 위해 생산자와 소비자의 동시 접근을 제어해야 한다.
동기화 메커니즘:
- 뮤텍스(Mutex): 공유 버퍼에 대한 상호 배제를 보장하기 위해 사용된다.
- 세마포어(Semaphore): 버퍼의 가용 공간과 채워진 공간을 추적하고 동기화하기 위해 사용된다.
- 조건 변수(Condition Variable): 생산자와 소비자 간의 통신과 조건 동기화를 위해 사용된다.
해결 방법:
- 생산자:
- 버퍼에 데이터를 추가하기 전에 버퍼가 가득 찼는지 확인한다.
- 버퍼가 가득 찼으면 소비자가 데이터를 소비할 때까지 대기한다.
- 버퍼에 여유 공간이 있으면 데이터를 추가하고, 대기 중인 소비자에게 신호를 보낸다.
- 소비자:
- 버퍼에서 데이터를 읽어오기 전에 버퍼가 비어있는지 확인한다.
- 버퍼가 비어있으면 생산자가 데이터를 생성할 때까지 대기한다.
- 버퍼에 데이터가 있으면 데이터를 읽어오고, 대기 중인 생산자에게 신호를 보낸다.
- 생산자:
동기화 과정:
- 생산자와 소비자는 공유 버퍼에 접근할 때 뮤텍스를 사용하여 상호 배제를 보장한다.
- 생산자는 버퍼에 데이터를 추가할 때 “채워진 공간” 세마포어를 증가시키고, 소비자는 데이터를 읽어올 때 “채워진 공간” 세마포어를 감소시킨다.
- 생산자는 버퍼가 가득 찼을 때 “가용 공간” 세마포어를 사용하여 대기하고, 소비자는 버퍼가 비어있을 때 “채워진 공간” 세마포어를 사용하여 대기한다.
- 조건 변수를 사용하여 생산자와 소비자 간의 통신과 조건 동기화를 처리한다.
- 생산자-소비자 문제는 실제 시스템에서 다양한 형태로 응용될 수 있다.
- 예를 들어, 네트워크 패킷 처리, 작업 스케줄링, 데이터베이스 처리 등에서 생산자-소비자 모델을 활용하여 병행성을 향상시키고 시스템의 성능을 개선할 수 있다.
생산자-소비자 문제 sample
buffer의 크기가 무한한 경우
- 2 Consumer, 1 Producer의 경우 발생하는 문제 (CASE 2)
- Solution
- Consumer는 오직 Producer만 깨우고, Producer는 오직 Consumer만 깨우도록 변경
Semaphore를 통한 Producer/Consumer problem Solution
Disadvantage of Semaphore
- 세마포어(Semaphore)는 병행 프로그래밍에서 유용한 도구이지만, 몇 가지 단점과 주의해야 할 점이 있다.
복잡성 증가:
- 세마포어를 사용하면 프로그램의 복잡성이 증가할 수 있다.
- 세마포어의 초기값 설정, 획득(wait) 및 해제(signal) 연산의 위치와 순서를 신중히 고려해야 한다.
- 세마포어를 잘못 사용하면 교착 상태(Deadlock), 경쟁 조건(Race Condition) 등의 문제가 발생할 수 있다.
자원 낭비 가능성:
- 세마포어를 사용할 때 스레드가 자원을 기다리는 동안 블록(Block)될 수 있다.
- 블록된 스레드는 CPU 시간을 소비하지 않지만, 스레드가 블록되는 동안 다른 작업을 수행할 수 없다.
- 많은 수의 스레드가 세마포어를 기다리는 경우 자원 낭비가 발생할 수 있다.
우선순위 역전 문제:
- 세마포어는 기본적으로 스레드의 우선순위를 고려하지 않는다.
- 우선순위가 낮은 스레드가 세마포어를 획득하고 있으면, 우선순위가 높은 스레드가 대기하게 되는 우선순위 역전 문제가 발생할 수 있다.
- 이로 인해 시스템의 실시간성이나 응답성이 저하될 수 있다.
불필요한 블로킹:
- 세마포어는 스레드 간의 동기화를 위해 사용되지만, 때로는 불필요한 블로킹을 유발할 수 있다.
- 예를 들어, 스레드가 이미 사용 가능한 자원을 기다리는 경우에도 세마포어로 인해 블록될 수 있다.
- 이는 성능 저하와 자원 활용의 비효율성을 초래할 수 있다.
데드락(Deadlock) 가능성:
- 세마포어를 사용할 때 데드락에 빠질 가능성이 있다.
- 둘 이상의 스레드가 서로 다른 세마포어를 기다리는 상황에서 순환 대기(Circular Wait) 조건이 발생하면 데드락이 발생할 수 있다.
- 데드락은 시스템의 응답이 멈추고 자원이 낭비되는 심각한 문제를 야기할 수 있다.
스레드 간의 통신 제한:
- 세마포어는 스레드 간의 동기화를 제공하지만, 스레드 간의 직접적인 통신 수단은 제공하지 않는다.
- 스레드 간에 데이터를 교환하거나 상태를 공유하기 위해서는 별도의 메커니즘(예: 공유 메모리, 메시지 패싱 등)을 사용해야 한다.
세마포어의 오용:
- 세마포어를 부적절하게 사용하면 프로그램의 정확성과 안정성에 문제가 발생할 수 있다.
- 예를 들어, 세마포어의 초기값을 잘못 설정하거나, 획득과 해제 연산의 균형이 맞지 않으면 예기치 않은 동작이 발생할 수 있다.
- 세마포어는 병행 프로그래밍에서 유용한 도구이지만, 사용할 때는 주의가 필요하다.
Reader-Writer Problem
- 병행 프로그래밍에서 공유 자원에 대한 읽기와 쓰기 연산을 수행하는 스레드 간의 동기화를 다루는 고전적인 문제이다.
- 이 문제에서는 여러 개의 Reader 스레드와 하나 이상의 Writer 스레드가 공유 자원에 동시에 접근할 때 발생하는 동기화 이슈를 해결하는 것이 목표이다.
문제 정의:
- 공유 자원에는 여러 개의 Reader 스레드와 하나 이상의 Writer 스레드가 접근할 수 있다.
- Reader 스레드는 공유 자원을 읽기만 하고 수정하지 않는다.
- Writer 스레드는 공유 자원을 읽고 수정할 수 있다.
- 여러 개의 Reader 스레드는 동시에 공유 자원을 읽을 수 있다.
- Writer 스레드가 공유 자원을 수정하는 동안에는 다른 스레드(Reader 또는 Writer)가 접근할 수 없어야 한다.
문제의 조건:
- Reader 스레드는 공유 자원을 동시에 읽을 수 있어야 한다.
- Writer 스레드는 공유 자원을 배타적으로 접근해야 한다.
- Reader 스레드와 Writer 스레드 간의 상호 배제가 보장되어야 한다.
- 스레드 간의 공평성이 보장되어야 한다.(특정 스레드가 계속 대기하는 상황(Starvation)이 발생하지 않아야 함)
해결 방법 - Reader 우선 방식:
해결 방법 - Writer 우선 방식:
동기화 메커니즘:
- 세마포어(Semaphore)나 뮤텍스(Mutex)를 사용하여 Reader 스레드와 Writer 스레드 간의 동기화를 구현할 수 있다.
- 공유 자원에 대한 접근을 제어하기 위해 읽기 세마포어와 쓰기 세마포어를 사용할 수 있다.
- 읽기 세마포어는 여러 개의 Reader 스레드가 동시에 접근할 수 있도록 허용하고, 쓰기 세마포어는 Writer 스레드의 배타적인 접근을 보장한다.
고려 사항:
- Reader 우선 방식에서는 Writer 스레드가 기아 상태(Starvation)에 빠질 수 있습니다. 지속적으로 Reader 스레드가 도착하면 Writer 스레드는 무한정 대기할 수 있다.
- Writer 우선 방식에서는 Reader 스레드가 기아 상태에 빠질 수 있습니다. Writer 스레드가 계속해서 도착하면 Reader 스레드는 무한정 대기할 수 있다.
- 공평성을 보장하기 위해 Reader 스레드와 Writer 스레드에 균등한 기회를 제공하는 방식으로 구현할 수도 있다.
- Reader 스레드와 Writer 스레드의 우선순위와 공평성을 고려해야 합니다. 또한, 스레드 간의 상호 배제와 기아 상태 방지를 위한 적절한 전략이 필요하다.