# 개요 ----
"세마포어" 사용해서 스레드 기반 동시 프로그래밍의 "경쟁 상태(race condition)" 문제 해결하기
- 일반적으로 os가 쓰레드 정확한 순서의 선택 여부를 예측할 수 없음
# 용어 정리 ----
* 세마포어 : 공유자원의 사용 가능 개수를 나타내는 정수 변수 (전역)
- 공유자원에 대한 접근
- 스레드 간 실행순서 동기화
* 경쟁 상태 : 여러 스레드/프로세스가 공유 데이터를 R/W 작업 할 때,
실행 순서에 따라서 잘못된 값을 읽거나 쓰게 되는 상황
* 임계구역 : 한 번에 오직 하나만 접근 가능한 구역
# 세마포어 vs 뮤텍스 ----
* 둘다 한 시점에 오직 하나의 스레드만 공유자원에 들어가도록 보장
/ 세마포어 (from. 다익스트라)
: 일종의 정수 카운터변수 (0->대기, P->-1, V->+1)
> 초기값: 자원 M개일때 M , 상호배제용일때 1 (0또는 1 --> 뮤텍스처럼 동작)
> 용도: 자원 수 제어, 동기화
/ 뮤텍스
: 락 객체 (초기값: 1 (locked / unlocked 둘 중 하나)) // 순수 lock 객체
- 용도: 상호배제 전용
- 락 소유자만 unlock 가능
# 세마포어 기본 원리 ----
: 오직 2가지 "원자적 연산"을 통해서만 접근할 수 있는 정수변수 S 로 구성됨.
- 이 연산들이 진행되는 동안에는 절대 다른 스레드에 의해 방해받지 않음
- 기본 작동 원리는 Mutex(상호 배제 알고리즘) 기반.
1) P 연산 (자원 사용하기 전에 획득 요청 -> S가 0보다 작아지면 대기)
S = S-1;
2) V 연산 (자원 사용 마친 후 반납 -> 대기중인 스레드 있음 깨우기)
S = S+1;
# 세마포어 정수 S의 의미 -----
* 양수 _ 현재 사용 가능한 공유 자원의 개수
* 0 _ 사용 가능한 자원이 없음
* 음수 _ 현재 자원 기다리며 대기중인 스레드 개수
* 보통 초기값: 1
# 실제 사용 방법
#include <sempaphore.h>
sem_t mutex; //전역변수 (모든 스레드가 접근할 수 있어야함, 보호하려는 공유변수와 같은 스코프에 존재해야함)
# problem ====
for (i=0; i<niters; i++)
cnt++;
>>>>>>
T1: L(cnt=0)
T2: L(cnt=0)
T1: U(1)
T2: U(1)
T1: S(1)
T2: S(1)
결과: cnt = 1 (증가가 하나 날아감!)
===========
# sem ver. ====
sem_t mutex; // 세마포어 선언 (전역 변수)
sem_init(&mutex, 0, 1); // 초기값 1 → binary semaphore (즉, 뮤텍스처럼)
void *thread(void *vargp) {
long i, niters = *((long *)vargp);
for (i = 0; i < niters; i++) {
P(&mutex); // 🔒 세마포어 잠금 (임계 구역 진입 전) //s:0
cnt++; // 🔥 공유 변수 접근 (critical section)
V(&mutex); // 🔓 세마포어 해제 (임계 구역 빠져나올 때) //s:1
}
return NULL;
}
# 12.5.1- 진행그래프 ----
: 여러 스레드가 동시에 실행될 때, 그 진행상태를 시각적으로 표현하는 도구
- 스레드들의 동시 실행, 잠재적 경쟁상태 시각적 이해 도구
==== 공유 변수에 Write 접근 시,
LOAD -> UPDATE -> STORE
이 과정을 겪는데, 이 과정에서 다른 스레드랑 겹치게 진행되면 꼬임.
> 각 공유자원에 세마포어(s) 연결해 씀 (보통 하나의 독립된 공유데이터묶음마다 1개씩)
# 세마포어로 공유자원 스케쥴링 ======
* 세마포어 역할: 위에서처럼 상호 배제 제공도 하지만,
공유자원으로의 접근을스케쥴링도 함
>>> 그 예시: 생산자-소비자 , reader-writer
# 생산자-소비자 ====
* 생산자: 물건을 만들어서 버퍼에 넣는 스레드
* 소비자: 버퍼에서 물건을 꺼내 쓰는 스레드
>> 동시 접근하면 엉망됨 (경쟁상태)
- 버퍼가 꽉 찼는데 생산자가 또 넣으면?
→ 버퍼 overflow 🚫 - 버퍼가 비었는데 소비자가 꺼내려 하면?
→ underflow 🚫 - 둘이 동시에 버퍼 수정하면?
→ race condition 🔥

#define N 5 // 버퍼 크기
int buffer[N]; // 공유 버퍼
int in = 0, out = 0; // 생산, 소비 위치 인덱스
sem_t mutex; // 상호배제용 binary 세마포어
sem_t empty; // 남은 빈칸 수
sem_t full; // 찬 칸 수
void *producer(void *arg) {
int item;
while (1) {
item = produce_item(); // 🍞 물건 하나 만듦
P(&empty); // 빈칸이 있을 때까지 대기
P(&mutex); // 버퍼 잠금
buffer[in] = item; // 버퍼에 넣기
in = (in + 1) % N; // 원형 버퍼 인덱스 갱신
V(&mutex); // 버퍼 잠금 해제
V(&full); // 물건 하나 늘었음 알림
}
}
void *consumer(void *arg) {
int item;
while (1) {
P(&full); // 찬 칸이 있을 때까지 대기
P(&mutex); // 버퍼 잠금
item = buffer[out]; // 버퍼에서 꺼내기
out = (out + 1) % N; // 인덱스 갱신
V(&mutex); // 잠금 해제
V(&empty); // 빈칸 하나 생겼다고 알림
consume_item(item); // 🍴 소비
}
}
>> 공유버퍼 두고 싸우는 두 스레드를 질서있게 만들어주는 empty, full, mutex 3 세마포어 동작
----> empty: 빈자리 통제 / full: 물건 통제 / mutex: 손 안겹치게
>>>> 이렇게 buffer,in,out,empty,full,mutex 이런걸 한번에 쓰는게 sbuf_t 구조체
#include "csapp.h"
#include "sbuf.h"
void sbuf_init(sbuf_t *sp, int n) {
sp->buf = Calloc(n, sizeof(int)); // 버퍼 메모리 동적할당
sp->n = n; // 버퍼 크기
sp->front = sp->rear = 0; // front/rear 인덱스 초기화
Sem_init(&sp->mutex, 0, 1); // 뮤텍스 세마포어 (상호배제)
Sem_init(&sp->slots, 0, n); // 남은 슬롯(empty) 세마포어
Sem_init(&sp->items, 0, 0); // 채워진 슬롯(full) 세마포어
}
void sbuf_deinit(sbuf_t *sp) {
Free(sp->buf); // 버퍼 메모리 해제
}
void sbuf_insert(sbuf_t *sp, int item) {
P(&sp->slots); // 🔒 남은 공간 확보 (empty--)
P(&sp->mutex); // 🔒 뮤텍스 잠금
sp->buf[(++sp->rear) % (sp->n)] = item; // 아이템 넣기
V(&sp->mutex); // 🔓 뮤텍스 해제
V(&sp->items); // 🔓 아이템 하나 추가 (full++)
}
int sbuf_remove(sbuf_t *sp) {
int item;
P(&sp->items); // 🔒 아이템 존재 대기 (full--)
P(&sp->mutex); // 🔒 버퍼 잠금
item = sp->buf[(++sp->front) % (sp->n)]; // 아이템 꺼내기
V(&sp->mutex); // 🔓 버퍼 잠금 해제
V(&sp->slots); // 🔓 빈 공간 증가 (empty++)
return item;
}
====
typedef struct {
int *buf; // 공유 버퍼 배열
int n; // 버퍼 크기
int front; // 소비자 포인터
int rear; // 생산자 포인터
sem_t mutex; // 상호배제용 binary 세마포어
sem_t slots; // empty count
sem_t items; // full count
} sbuf_t;
>>> 이거 이용해 생산자-소비자 나타내면,
sbuf_t sbuf;
int main() {
sbuf_init(&sbuf, 16); // 버퍼 크기 16
pthread_create(..., producer, NULL);
pthread_create(..., consumer, NULL);
}
void *producer(void *arg) {
while (1) {
int item = produce_item();
sbuf_insert(&sbuf, item); // 동기화된 삽입 함수
}
}
void *consumer(void *arg) {
while (1) {
int item = sbuf_remove(&sbuf); // 동기화된 제거 함수
consume_item(item);
}
}
reader-writer
🎬 1️⃣ 문제의 시나리오
- 여러 reader(읽는 사람) 와
- 여러 writer(쓰는 사람) 가
하나의 공유 자원(예: 파일, 데이터베이스, 메모리) 을 동시에 접근하려고 해요.
⚠️ 2️⃣ 규칙 (이 문제의 핵심 규율)
1️⃣ 여러 reader들은 동시에 읽어도 괜찮아요.
읽기는 데이터 안 바꾸니까, conflict 없음.
2️⃣ 하지만 writer가 쓸 땐 혼자만 들어와야 해요.
쓰는 동안 누가 읽거나 또 쓰면, 데이터 일관성 깨져요 💀
즉 👇
| 여러 reader | ✅ O | 읽기만 함 |
| 여러 writer | ❌ X | 데이터 망가짐 |
| reader + writer 동시에 | ❌ X | writer가 변경 중일 수 있음 |
🧩 3️⃣ 이걸 세마포어로 제어하려면?
보통 이렇게 2가지 자원이 필요해요 👇
| mutex | 1 | readcnt (읽는 사람 수) 보호 |
| w | 1 | writer(또는 reader 전체) 접근 제어 |
그리고 공유 변수 하나 추가 👇
하지만 쓰는 놈(writers)은 한 놈씩 차례로.
'STUDY > 정글' 카테고리의 다른 글
| [네트워크] TCP 소켓 통신 구조 정리 (0) | 2025.11.05 |
|---|---|
| [웹서버] echo 서버 구현 (0) | 2025.11.05 |
| [TIL] 포인터 문제풀이(2) - G2G (0) | 2025.10.15 |
| [정글] W5 퀴즈 (주요 내용 정리) (0) | 2025.10.14 |
| [C] 포인터 문제풀이 (0) | 2025.10.11 |