본문 바로가기

Etc/Study

Semaphore

반응형

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와 동일한 개념으로 동작합니다! 🚀

 

 

 

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