✅ Semaphore의 정의 (CS 관점)
➡ **Semaphore(세마포어)**는 멀티스레딩 및 병렬 프로그래밍에서 공유 리소스의 접근을 제한하는 동기화 기법입니다.
➡ 공유 리소스(예: 네트워크 연결, 데이터베이스, 파일 등)에 대한 동시 접근을 제어하기 위해 사용됩니다.
🔥 1️⃣ Semaphore란? (CS 기본 개념)
➡ Semaphore는 "허가증"을 관리하는 카운터입니다.
➡ N개의 허가증(Tickets)을 가지며, 각 스레드(Task)가 실행되기 위해서는 하나의 허가증을 획득해야 합니다.
➡ 허가증이 남아 있으면 스레드(Task)가 실행될 수 있으며, 허가증이 없으면 대기(Blocking) 상태가 됩니다.
✅ Semaphore 동작 개념
상황Semaphore 동작
허가증 있음 (count > 0) | ✅ 스레드(Task) 실행 (count--) |
허가증 없음 (count == 0) | ❌ 스레드(Task) 대기 |
실행 완료 후 | ✅ 허가증 반환 (count++) |
📌 즉, Semaphore는 "동시에 실행 가능한 개수"를 조절하는 도구입니다.
🔥 2️⃣ Binary Semaphore vs Counting Semaphore
📌 Semaphore는 크게 두 가지 종류가 있습니다.
✅ 1. Binary Semaphore (이진 세마포어)
➡ 뮤텍스(Mutex)와 유사하며, 동시에 하나의 작업만 수행 가능한 경우 사용됩니다.
➡ count 값이 0 또는 1만 가질 수 있습니다. (즉, 한 번에 하나의 스레드만 실행 가능)
🔹 사용 예시: 하나의 파일에 한 번에 하나의 프로세스만 접근 가능하도록 제한.
Semaphore s = 1; // 초기 값 1 (하나의 프로세스만 실행 가능)
P(s): // 자원 사용 요청
if (s == 1) {
s = 0; // 허가증 획득 (다른 프로세스 접근 불가)
} else {
wait(); // 허가증이 없으면 대기
}
V(s): // 자원 반환
s = 1; // 허가증 반환 (다른 프로세스 실행 가능)
✅ Binary Semaphore는 Mutex(Mutual Exclusion)와 유사하지만, owner 개념이 없어 더 유연합니다.
✅ 2. Counting Semaphore (카운팅 세마포어)
➡ 허가증(count)을 1보다 크게 설정할 수 있으며, 여러 개의 프로세스가 동시에 실행 가능합니다.
➡ count 값은 동시에 실행 가능한 최대 프로세스 개수를 의미합니다.
🔹 사용 예시: 동시에 최대 5개의 네트워크 요청만 허용하기
Semaphore s = 5; // 최대 5개의 프로세스 실행 가능
P(s):
if (s > 0) {
s--; // 허가증 획득
} else {
wait(); // 허가증이 없으면 대기
}
V(s):
s++; // 허가증 반환 (다른 프로세스 실행 가능)
✅ Counting Semaphore는 동시 실행 개수를 조절하는 역할을 하며, asyncio.Semaphore(N)과 유사합니다.
🔥 3️⃣ Semaphore vs Mutex 차이점
📌 Semaphore와 Mutex는 유사하지만, 다음과 같은 차이점이 있습니다.
구분SemaphoreMutex
허용 개수 | ✅ 여러 개 (count > 1 가능) | ❌ 하나 (count = 1 만 가능) |
소유권 (Ownership) | ❌ 없음 (누구나 해제 가능) | ✅ 있음 (잠근 스레드만 해제 가능) |
Blocking 방식 | ✅ 대기 중인 스레드는 깨어남 | ✅ 소유한 스레드만 해제 가능 |
사용 예시 | 동시 실행 개수 제한 (예: 네트워크 요청, DB 연결) | 단일 리소스 보호 (예: 파일 쓰기, 임계 영역 보호) |
💡 즉, Semaphore는 "동시 실행 개수 제한"을 위해, Mutex는 "단일 리소스 보호"를 위해 사용됩니다. 🚀
🔥 4️⃣ Python asyncio.Semaphore와 CS Semaphore 비교
Python의 asyncio.Semaphore(N)는 CS의 Counting Semaphore와 유사합니다.
import asyncio
semaphore = asyncio.Semaphore(3) # ✅ 동시에 최대 3개의 작업 실행 가능
async def task(n):
async with semaphore: # ✅ 허가증 획득 (없으면 대기)
print(f"Task {n} 시작")
await asyncio.sleep(1)
print(f"Task {n} 완료")
async def main():
tasks = [task(i) for i in range(10)] # 10개의 Task 실행
await asyncio.gather(*tasks) # ✅ 동시에 3개씩 실행됨
asyncio.run(main())
📌 결과 예시 (동시에 3개씩 실행됨)
Task 0 시작
Task 1 시작
Task 2 시작
Task 0 완료
Task 3 시작
Task 1 완료
Task 4 시작
...
✅ Python의 asyncio.Semaphore는 Counting Semaphore와 동일한 개념으로 동작합니다.
✅ 즉, 여러 개의 task가 동시에 실행될 수 있도록 허용하며, 지정된 개수(N)를 초과하면 대기 상태가 됩니다. 🚀
🚀 최종 정리
질문정답
Semaphore란? | ✅ 멀티스레딩 및 비동기 프로그래밍에서 공유 리소스 접근을 제한하는 동기화 기법 |
Binary Semaphore vs Counting Semaphore? | ✅ Binary는 count=1, Counting은 count > 1 가능 (동시 실행 개수 조절 가능) |
Semaphore vs Mutex 차이점? | ✅ Semaphore는 소유권이 없고, 동시 실행을 제한 / Mutex는 단일 리소스를 보호 |
Python asyncio.Semaphore는 어떤 개념과 유사? | ✅ Counting Semaphore와 동일하며, 비동기 태스크에서 동시 실행 개수를 제한하는 역할을 함 |
💡 즉, Semaphore는 CS에서 공유 리소스 접근을 제한하는 동기화 기법이며, Python의 asyncio.Semaphore(N)는 Counting Semaphore와 동일한 개념으로 동작합니다! 🚀
✅ asyncio.Semaphore가 제한하는 것은 무엇인가?
➡ 우리가 asyncio.Semaphore(N)를 사용한 코드에서 제한하는 것은 OS 스레드가 아니라 태스크(Task)입니다.
➡ 즉, 단일 스레드에서 동시에 실행될 수 있는 비동기 태스크(Task)의 개수를 제한하는 역할을 합니다.
🔥 1️⃣ Counting Semaphore의 일반적인 개념
📌 OS에서 사용하는 Counting Semaphore의 정의:
동시에 실행 가능한 최대 프로세스(또는 스레드)의 개수를 제한하는 동기화 기법.
📌 하지만 asyncio.Semaphore는 다름.
- Python의 asyncio는 비동기(Asynchronous) 프레임워크이며, 기본적으로 단일 스레드에서 실행됨.
- 따라서 우리가 사용하는 asyncio.Semaphore는 "동시에 실행 가능한 태스크(Task)의 개수"를 제한하는 역할을 함.
🔥 2️⃣ asyncio.Semaphore는 태스크(Task)를 제한함
📌 코드를 다시 살펴보면
import asyncio
semaphore = asyncio.Semaphore(3) # ✅ 한 번에 최대 3개의 태스크만 실행 가능
async def worker(n):
async with semaphore: # ✅ 동시 실행 개수 제한 적용 (태스크 단위로 제한됨)
print(f"Task {n} 시작")
await asyncio.sleep(2)
print(f"Task {n} 완료")
async def main():
tasks = [worker(i) for i in range(10)] # 10개의 태스크 생성
await asyncio.gather(*tasks) # ✅ 동시 실행 개수 제한 적용
asyncio.run(main())
📌 출력 결과
Task 0 시작
Task 1 시작
Task 2 시작
(2초 후)
Task 0 완료
Task 3 시작
Task 1 완료
Task 4 시작
Task 2 완료
Task 5 시작
...
✅ 즉, 한 번에 최대 3개의 태스크만 실행되도록 제한됨.
✅ 각 태스크가 async with semaphore: 블록을 지나야 실행 가능하며, 동시에 실행되는 태스크 개수가 3개를 초과하지 않도록 조절됨.
🔥 3️⃣ asyncio.Semaphore는 OS 스레드를 제한하는가?
🚨 아니요, asyncio.Semaphore는 OS 스레드를 제한하지 않습니다.
- 우리가 실행하는 asyncio 코드 자체가 단일 스레드에서 실행되므로, OS 스레드 개수를 제어할 필요가 없음.
- 제한하는 것은 비동기 태스크(Task)의 개수이며, OS의 멀티스레딩과는 관계없음.
📌 OS 스레드를 제한하려면 ThreadPoolExecutor 또는 multiprocessing을 사용해야 함
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
executor.submit(some_function) # ✅ OS 스레드 제한
✅ 이렇게 하면 실제 OS에서 실행되는 스레드 개수를 제한할 수 있음.
✅ 그러나 asyncio.Semaphore는 스레드가 아니라 태스크 개수를 제한하는 역할을 함. 🚀
🔥 4️⃣ asyncio.Semaphore는 태스크(Task)와 연결된 I/O 리소스를 제한하는 역할도 함
➡ asyncio.Semaphore는 단순히 태스크 개수만 제한하는 것이 아니라, 제한된 리소스의 동시 접근을 제어하는 역할도 수행함.
➡ 예를 들어, 동시에 실행 가능한 HTTP 요청 수를 제한하려면 Semaphore를 사용할 수 있음.
📌 HTTP 요청 개수 제한 (동시에 최대 5개 요청)
import aiohttp
import asyncio
semaphore = asyncio.Semaphore(5) # ✅ 동시에 5개의 HTTP 요청만 허용
async def fetch(url):
async with semaphore: # ✅ HTTP 요청 개수 제한
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = ["https://example.com"] * 20 # 20개의 요청
tasks = [fetch(url) for url in urls]
results = await asyncio.gather(*tasks)
print(f"응답 개수: {len(results)}") # ✅ 20개 요청 완료
asyncio.run(main())
📌 동작 방식
- 한 번에 최대 5개의 HTTP 요청만 실행됨.
- 새로운 요청을 하려면 기존 요청 중 하나가 완료될 때까지 대기해야 함.
- 이를 통해 API 서버에 과부하를 주지 않고, 일정 개수의 요청만 동시에 유지 가능.
✅ 즉, asyncio.Semaphore는 단순히 태스크(Task) 개수를 제한하는 것이 아니라, "동시에 접근 가능한 리소스"를 제한하는 역할도 수행함.
🚀 최종 정리
질문정답asyncio.Semaphore는 OS 스레드를 제한하는가? | ❌ 아니요. asyncio는 단일 스레드에서 동작하며, OS 스레드를 직접 제어하지 않음. |
asyncio.Semaphore는 무엇을 제한하는가? | ✅ "동시에 실행 가능한 비동기 태스크(Task) 개수"를 제한함. |
왜 asyncio.Semaphore가 필요한가? | ✅ 리소스(예: HTTP 요청, DB 연결 등)의 과부하를 방지하고, 일정한 동시 실행 개수를 유지하기 위해. |
OS에서의 Counting Semaphore와 차이점은? | ✅ OS에서는 스레드/프로세스를 제한하지만, asyncio.Semaphore는 비동기 태스크(Task)를 제한함. |
💡 즉, asyncio.Semaphore는 단일 스레드 환경에서 동시 실행되는 비동기 태스크 개수를 제한하는 역할을 하며, OS의 Counting Semaphore와 개념적으로 비슷하지만 실행 단위(Task vs Thread)가 다릅니다! 🚀
'Etc > Study' 카테고리의 다른 글
자료구조 [Queue, Stack, Heap] (0) | 2025.03.27 |
---|---|
RAG (Retrieval-Augmented Generation) (0) | 2025.03.27 |
Thread & Task (0) | 2025.03.27 |
thread & event loop & task (0) | 2025.03.04 |