[운영체제] Day3 프로세스 통신과 동기화(2)

빵코·2024년 4월 5일

동기화

프로세스들은 서로 독립적이지만, 프로세스 간 통신을 하거나 같은 대상에 대한 작업을 함으로써 협력할 수 있다. 그런데 이때, 동시다발적으로 작업을 처리하면 문제가 발생 할 수 있다.
이를 위해 프로세스 동기화가 필요하고, 스레드간에도 이것이 적용된다.

공유자원과 임계구역

프로세스 간 통신에서는 공동으로 이용하는 변수가 파일, 입출력 기기 등이 존재하고
이를 가리켜 '공유자원'이라 한다.
공유자원은 각 프로세스의 접근 순서에 따라 결과가 달라질 수 있는데, 프로세스가 동시에 실행할 경우 문제가 발생할 수 있는 영역을 가리켜 '임계구역'이라고 한다.

예를들어 프로세스A는 +1시키고, 프로세스B는 -1시킬 때 공유자원에 하나씩 접근하면 다시 0이되지만 동시에 접근하게 되면 +1만이 넣어지거나 -1만이 넣어질 수 있다.

  • 상호배제
    동기화기법은 임계구역에서 발생 할 수 있는 문제를 해결하기 위한 기법이고,
    이 동기화기법 구현 시에는 '상호배제'라는 조건을 만족시켜야 함.
    상호배제란, 하나의 프로세스가 임계구역에 들어갔다면 다른 프로세스는 임계구역에 들어갈 수 없다는 조건을 뜻함.

<동기화 기법들>

- 뮤텍스 락

뮤텍스락은 동시에 접근되면 안되는 임계구역에 동시에 접근 할 수 없도록 만드는 도구이다.
임계구역에 진입하는 프로세스는 뮤텍스락을 이용해 임계구역의 문을 잠글 수 있음.
뮤텍스락은 두개의 대표적인 함수를 호출해 동작을 수행함
: - acquire(): 프로세스가 임계구역에 진입하기 전에 호출하는 문잠그기 함수

  • release(): 임계구역에서 볼일을 마치고 나올 때 문잠금 해제하는 함수

- 세마포어

뮤텍스락은 공유자원이 1개있을 때라 생각하고 만든 동기화 방법이지만,
세마포어는 공유자원이 여러개 있을 때도 사용 가능한 동기화 방법임.
어떤 프로세스가 아무도 이용하지 않는 공유자원1에 접근하려 할 때 운영체제는 세마포어라는 열쇠를 그 프로세스에게 건네주어 공유자원1에 접근가능하게 함.
새로운 프로세스가 와서 공유자원1에 접근하려 할 때 이미 열쇠(세마포어)이용중이니 기다리라고 함
세마포어도 두개의 대표적인 함수를 호출해 동작을 수행함
: - wait(): 프로세스가 임계구역에 들어갈 수 있는지 기다려야하는지 알려주는 함수

  • signal(): 기다리고있는 프로세스에게 이제 들어가도 된다고 신호전달해주는 함수

실습코드

<뮤텍스락 걸어주지 않아 생기는 문제 확인>

  from multiprocessing import Process, Value
  # Value: 프로세스간의 값을 공유하게 만들고 싶을 때 사용하는 파이썬의 생성자함수

  def counter1(snum, cnt): # snum:공유하는숫자, cnt:몇번연산을 수행할지
      for i in range(cnt):
          snum.value += 1

  def counter2(snum, cnt): # snum:공유하는숫자, cnt:몇번연산을 수행할지
      for i in range(cnt):
          snum.value -= 1

  if __name__ == "__main__" :

      shared_number = Value('i', 0) # int로 만들고, 초기값이 0
      p1 = Process(target=counter1, args=(shared_number,5000))
      p1.start()

      p2 = Process(target=counter2, args=(shared_number,5000))
      p2.start()

      p1.join()
      p2.join()

      print("finally, number is", shared_number.value)

<실행결과>

-5000 +5000 하면 0이되야 맞지만, 동기화오류로 625가 나옴.

<뮤텍스 락을 걸어준 코드>

  from multiprocessing import Process, Value, Lock
# Value: 프로세스간의 값을 공유하게 만들고 싶을 때 사용하는 파이썬의 생성자함수

def counter1(snum, cnt, lock): # snum:공유하는숫자, cnt:몇번연산을 수행할지
    lock.acquire() # 처음들어올 때 락을 걸고
    try:
        for i in range(cnt):
            snum.value += 1
    finally:
        lock.release() #문제없다면 release 해줌

def counter2(snum, cnt, lock): # snum:공유하는숫자, cnt:몇번연산을 수행할지
    lock.acquire() # 처음들어올 때 락을 걸고
    try:
        for i in range(cnt):
            snum.value -= 1
    finally:
        lock.release() #문제없다면 release 해줌

if __name__ == "__main__" :

    lock = Lock()
    shared_number = Value('i', 0) # int로 만들고, 초기값이 0
    p1 = Process(target=counter1, args=(shared_number,5000, lock))
    p1.start()

    p2 = Process(target=counter2, args=(shared_number,5000,lock))
    p2.start()

    p1.join()
    p2.join()

    print("finally, number is", shared_number.value)

<실행결과>

p1이 실행할 때 p2 실행 안했고, p2 실행시 p1실행 안했음.

<스레드에 대해 뮤텍스 락 제공>

 import threading
 from multiprocessing import Process, Value, Lock
 # Value: 프로세스간의 값을 공유하게 만들고 싶을 때 사용하는 파이썬의 생성자함수

 def counter1(snum, cnt, lock): # snum:공유하는숫자, cnt:몇번연산을 수행할지
     lock.acquire() # 처음들어올 때 락을 걸고
     try:
         for i in range(cnt):
             snum.value += 1
     finally:
         lock.release() #문제없다면 release 해줌

 def counter2(snum, cnt, lock): # snum:공유하는숫자, cnt:몇번연산을 수행할지
     lock.acquire() # 처음들어올 때 락을 걸고
     try:
         for i in range(cnt):
             snum.value -= 1
     finally:
         lock.release() #문제없다면 release 해줌

 if __name__ == "__main__" :

     lock = Lock()
     shared_number = Value('i', 0) # int로 만들고, 초기값이 0
     t1 = threading.Thread(target=counter1, args=(shared_number,10000, lock))
     t1.start()

     t2 = threading.Thread(target=counter2, args=(shared_number,10000,lock))
     t2.start()

     t1.join()
     t2.join()

     print("finally, number is", shared_number.value)

<실행결과>

profile
빵먹으면서 코딩하는 개발자를 꿈꾸는 코린이

0개의 댓글