CAFE

Python

Thread - semaphore 예제

작성자ParkYK|작성시간15.03.04|조회수576 목록 댓글 0

프로세스(Process)와 스레드(Thread)
 : 실행 중인 프로그램을 프로세스라고 하며, 스레드는 하나의 프로세스 내부에서 실행되는 하나의 실행 흐름(작업 단위)을 의미한다. 하나의 프로세스 안에서 여러 개의 스레드가 동시에 실행되는 구조를 멀티 스레드(Multi-thread)라고 한다.
 - 멀티 스레드는 전체 프로그램을 여러 부분 작업으로 나누어 처리할 수 있게 해 준다.
 - 각 작업이 서로 독립적이고 상호 의존성이 크지 않은 경우, 여러 작업을 동시에 수행함으로써 프로그램의 응답성과 처리 효율을 높일 수 있다.
 - 특히 작업의 실행 순서나 완료 시점이 예측하기 어려운 비동기적 상황에 적합하다.

* 프로세스와 스레드의 차이
  : 프로세스와 스레드는 모두 프로그램을 실행한다는 공통점이 있지만, 메모리 사용 방식에서 중요한 차이가 있다.

1) 프로세스
  - 운영체제에서 독립적으로 실행되는 실행 단위
  - 각 프로세스는 서로 분리된 메모리 공간을 사용
  - 한 프로세스의 오류가 다른 프로세스에 영향을 주지 않음

2) 스레드
  - 하나의 프로세스 내부에서 실행되는 실행 단위
  - 메모리 공간(힙, 전역 변수 등)을 공유
  - 데이터 공유와 통신이 빠르고 효율적

이 때문에 스레드는 프로세스 간 통신보다 가볍고 빠른 협업이 가능하지만, 동시에 동기화 문제에 주의해야 한다.

* 스레드의 실행 흐름과 스케줄링
 : 스레드는 일반적으로 시작 → 실행 → 종료의 생명주기를 가진다. 여러 스레드가 존재할 때, 한 스레드가 실행되는 동안 다른 스레드는 일시 중단될 수 있다. 이처럼 실행 권한을 다른 스레드에게 넘기는 것을 제어권 양도(yield)라고 한다.
 : 하나의 프로세스 내에 존재하는 모든 스레드는 메인 스레드와 동일한 데이터 공간을 공유한다. 이 때문에 스레드 간에는 결과를 쉽게 공유하고 협력적인 작업을 수행할 수 있다.

* 동시성과 실제 실행
 : 멀티 스레드는 동시에 실행되는 것처럼 보이지만, 단일 CPU 환경에서는 실제로 한 순간에 하나의 스레드만 실행된다. 운영체제는 짧은 시간 단위로 스레드를 번갈아 실행시키는 스케줄링을 통해 동시성을 구현한다. 이 과정에서 각 스레드는 자신의 작업을 조금씩 수행하고, 필요한 경우 다른 스레드의 실행 결과를 공유하면서 전체 프로세스를 완성해 나간다.

요약하면 
  - 프로세스는 독립적인 실행 단위
  - 스레드는 프로세스 내부의 실행 흐름
  - 멀티 스레드는 작업 분할과 동시성을 제공
  - 메모리 공유로 인해 빠르지만 동기화가 중요

 

 

 

세마포어(Semaphore)란? “동시에 몇 명까지 들어올 수 있는지 정해 놓은 신호등”이라고 생각하면 된다.
여러 스레드(또는 프로세스)가 같은 자원을 사용하려고 할 때, 아무 제한 없이 접근하면 충돌이 발생할 수 있다. 세마포어는 이런 상황에서 '동시에 접근 가능한 개수를 제한해서 충돌을 막는 도구'다.

왜 세마포어가 필요한가?
예를 들어, 프린터가 2대뿐인 사무실, DB 커넥션이 최대 5개만 허용되는 서버, 파일을 동시에 3명까지만 쓰게 하고 싶은 경우.
이런 상황에서 제한이 없으면 자원이 망가짐, 데이터가 꼬임, 프로그램이 비정상 종료됨
  → 그래서 “몇 개까지 허용할지”를 관리하는 장치가 필요 → 그게 바로 세마포어

* 세마포어의 핵심 개념
1. 동시에 허용할 개수 지정 - 세마포어는 생성자에서 허용 개수를 정한다.
    예: Semaphore(1) → 한 번에 1개만 허용 (사실상 Lock)
         Semaphore(3) → 최대 3개의 스레드까지 동시 실행 가능
2. acquire() – 들어가기 전에 허락 받기
   스레드가 자원을 사용하려고 할 때 “나 들어가도 돼?” 라고 물어보는 것이 acquire()다.
   허용 개수가 남아 있으면 → 바로 들어감
   허용 개수가 0이면 → 누군가 나올 때까지 대기
3. release() – 다 쓰고 나서 반납
   작업이 끝나면 반드시 “나 이제 나갈게, 자리 하나 비었어” 라고 알려야 한다. 이게 release()다.
   이걸 안 하면 허용 개수가 줄어든 채로 복구되지 않음, 다른 스레드들이 영원히 대기 상태 → 버그 발생

흐름을 간단히 요약하면 acquire() → 자원 사용 → release()  이 패턴이 세마포어 사용의 핵심 공식이다.

세마포어 vs 상호배제(Mutex)
구분                   Mutex                  Semaphore
동시에 허용 수   1개                        여러 개 가능
목적                  완전한 상호배제     제한된 동시 접근
예시                  화장실 1칸             주차장 10칸
세마포어는 “상호배제를 확장한 개념”이라고 보면 된다.

* 한정된 자원 관리 관점에서 정리
  : 한정된 자원을 여러 스레드(프로세스)가 사용해야 할 때 자원 사용 시 충돌을 방지하고 동시에 사용할 수 있는 개수를 제어하기 위해
세마포어를 사용한다. 세마포어는 동시에 접근 가능한 개수를 숫자로 관리하는 동기화 도구다.

  - 가장 오래된 동기화 primitive

  - 내부에 정수형 카운트 변수를 소유해서 동시에 실행할 수 있는 클래스의 개수를 설정할 수 있다.

  - 생성 시 동시에 수행할 스레드의 개수를 설정하고 작업을 시작하기 전에 acquire()를 호출해서 동작할 수 있으면 동작하고 카운터의 값을 1감소시키고 카운터의 값이 1보다 작으면 대기 상태로 간다.

  - release()를 호출하면 카운터의 1 증가한다.

 

예제1)

import threading, time

sema = threading.Semaphore(3)

    

class ThreadEx(threading.Thread):

    def run(self):

        sema.acquire()

        time.sleep(3)

        print(self.getName())

        sema.release()

 

for i in range(10):

    th = ThreadEx()

    th.start()

 

 

예제2)

from threading import BoundedSemaphore, Lock, Thread

from random import randrange

from time import sleep, ctime

from atexit import register

 

lock = Lock()

MAX = 5

candytray = BoundedSemaphore(MAX)

 

def refill():

    lock.acquire()

    print('Refilling candy...')

    try:

        candytray.release()

    except ValueError:

        print('full, skiping')

    else:

        print('OK')

    lock.release()

 

def buy():

    lock.acquire()

    print('Buying candy...')

    if candytray.acquire(False):

        print('OK')

    else:

        print('empty, skiping')

    lock.release()

 

def producer(loops):

    for i in range(loops):

        refill()

        sleep(randrange(3))

 

def consumer(loops):

    for i in range(loops):

        buy()

        sleep(randrange(3))     

 

def _main():

    print('starting at:', ctime())

    nloops = randrange(2, 6)

    print('The candy machine (full with %d bars)!'%MAX)

    Thread(target=consumer, args=(randrange(nloops, nloops + MAX + 2),)).start()

    Thread(target=producer, args=(nloops,)).start()

 

@register

def _atexit():

    print('all done at : ', ctime())

 

if __name__ == '__main__':

    _main()

 

 

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
맨위로

카페 검색

카페 검색어 입력폼