[운영체제] 스레드 - (2)

Yeongsan Son·2021년 7월 1일
0

스레드에서 발생하는 동기화 이슈에 대해서 알아보자.

다음과 같이 스레드 A, B, C가 있다고 가정해보자.

스레드 실행 순서 역시 스케쥴러가 관리하기 때문에 실행 순서가 유동적으로 바뀐다.

  • 읽고 쓰는 순서가 정해져 있지 않다

이에 따라서, 하나의 스레드에서 변수 값을 바꿔서 다른 스레드에서 사용하다가, 또 다른 스레드에서 변수 값을 바꿔 다른 스레드들이 사용하는 상황이 발생한다.

이를 동기화(Synchronization) 이슈라고 부르고 이러한 이슈로 스레드의 관리가 필요하다.

대부분의 동기화 이슈는 컨텍스트 스위칭에서 실행되어야할 함수가 누락되면서 발생한다.

  • 동기화 이슈가 발생하는 코드
    • 컨텍스트 스위칭시에 일부 스레드에서 thread_main 함수의 실행이 누락되어 실행할때마다 결과가 다르게 나타남
import threading

g_count = 0

def thread_main():
  global g_count # 전역변수를 사용하겠다
  for i in range(10000):
    g_count = g_count + 1
    
threads = [] 

for i in range(50): 
  th = threading.Thread(target = thread_main) 
  threads.append(th) # 50개의 스레드 생성
  
for th in threads:
  th.start() # 각각의 스레드 실행 => thread_main 함수 실행

for th in threads:
  th.join() # 각 스레드의 종료까지 대기
  
print('g_count = ', g_count)
  • 동기화 이슈를 핸들링한 코드
    • #2 :lock 함수가 임계 영역에 스레드가 들어가기 전, key를 얻어 for문을 실행
    • key는 하나이기 때문에, 다른 스레드가 key를 가지고 있다면, 이전 스레드의 종료까지 다음 스레드 대기
    • #3: 가지고 있던 key를 반환
import threading

g_count = 0

def thread_main():
  global g_count
  lock.acquire()  #2
  for i in range(10000):
    g_count = g_count + 1
  lock.release()  #3
    
lock = threading.Lock()  #1
threads = [] 

for i in range(50): 
  th = threading.Thread(target = thread_main) 
  threads.append(th) # 50개의 스레드 생성
  
for th in threads:
  th.start() # 각각의 스레드 실행 => thread_main 함수 실행

for th in threads:
  th.join() # 각 스레드의 종료까지 대기
  
print('g_count = ', g_count)

동기화 이슈

  • 동기화: 작업들 사이에 실행 시기를 맞추는 것
    • ex) 영화의 스크린과 자막의 싱크가 안맞는다
  • 여러 스레드가 동일한 자원(데이터)에 접근 시 동기화 이슈 발생
    • 동일 자원을 여러 스레드가 동시 수정 시, 각 스레드 결과에 영향을 미침

해결 방안

  • Mutual exclusion(상호 배제)
    • 임계 자원 (critical resource)
    • 임계 구역 (critical section)
  • 스레드는 프로세스 모든 데이터에 접근 가능
    • 여러 스레드가 변경한느 공유 변수에 대해 Exclusive Access가 필요
    • 어느 한 스레드가 공유 변수를 갱신하는 동안 다른 스레드가 동시에 접근하지 못하도록 block

동기화와 세마포어

  • 임계구역에 대한 접근을 막기 위해 LOCKING 메커니즘이 필요
    • mutex(binary semaphore)
      : 임계 구역에 하나의 스레드만 접근
      : 성능 상의 이슈가 있을 수 있음
    • Semaphore
      : 임계구역에 여러 스레드가 접근
      : counter를 두어서 동시에 리소스에 접근 가능한 스레드의 수를 제어

세마포어

  • P: 검사 (임계 영역 접근)
    • S값이 1이상이면, 임계 영역 진입 후, S값 1차감 (S값이 0이면 대기)
    • lock.acquire
  • V: 증가 (임계 영역에서 나올때)
    • S값을 1더하고, 임계 영역을 나옴
    • lock.release
  • S: 세마포어 값 (초기 값만큼 여러 프로세스가 동시 임꼐 영역 접근 가능)
  • 세마포어 수도 코드
P(s): wait(S) {
           while S <=0 // 대기
           ;
        S--; // 다른 프로세스 접근 제한
      }

V(S): signal(S) {
         S++;  // 다른 프로세스 접근 허용
      }

세마포어 - 바쁜대기

  • wait() 은 S가 0이라면, 임계 영역에 들어가기 위해, 반복문 수행
    • 바쁜 대기, busy wating
  • 수도코드
    • S가 1이 될 때까지 루프가 실행
P(S): wait(S) {
          while S <= 0 // 바쁜 대기
          ;
        S--; // 다른 프로세스 접근 제한
      }

운영체제 기술로 보완 - 대기 큐

  • S가 음수일 경우, 바쁜 대기 대신, 대기 큐에 넣는다
wait(S) {
    S-> count--;
  if(S->count < 0) {
    add this process to S->queue;
    block()
  }
}
  • wakeup() 함수를 통해 대기 큐에 있는 프로세스 재실행
signal(S) {
    S->count++; 
  if(S->count <= 0) {
    remove a process P from S->queue;
    wakeup(P)
  }
}
profile
매몰되지 않는 개발자가 되자

0개의 댓글