[OS] Synchronization & Critical sections & Synchronization Problem

2024. 4. 13. 20:40CS/Operating System

Synchronization

멀티쓰레드로 만들어진 프로그램들이 서로 잘 돌아가게 해야 한다.

  • 여러 자원을 공유하는 멀티 쓰레드 프로그램들이 서로 잘 협력해야 한다.
  • 멀티 쓰레드들이 실행되면서 문제를 일으킬 수 있는데, 그걸 Synchronization 문제라 한다.

협력을 통제해야만 문제가 생기지 않는다.

 

  • 두 개의 쓰레드가 있는 프로세스를 생각해 보자. 쓰레드들의 스케줄링은 OS 내부의 스케줄러가 결정한다.
    • 그런데 가끔, 쓰레드들의 실행 순서에 따라서 문제가 생기거나 결과가 바뀔 수 있다.
    • 운영체제의 스케줄링을 프로그래머가 결정할 수 없다.
  • 그런데 프로그래머는 스케줄링을 컨틀로 해야 할 때가 있다. 여러 쓰레드의 실행 순서, 프로세스들 간의 스케줄링을 컨트롤해야 할 때가 분명히 있다.

예시로 보자. 한 커플이 데이트 통장에 백만 원을 넣어놓고 쓰려고 했다. 그런데 그들 중 한 명이 혹은 둘이 동시에 배신을 하고 돈을 혼자 빼서 사용할 수 있다. 은행의 코드를 생각해 보자.

balance는 현재 잔고이고, amount 만큼 인출한다. 이 코드는 문제가 없다.

문제는 두 명이 서로 다른 ATM에서 동시에 백만 원에서 10만 원을 인출하려 할 때 문제가 생긴다.

  • 어떤 문제가 생길 수 있을지 생각해 보자.
  • 처음 남자의 코드가 실행되면 balance가 90이 된다. 아직 put_balance()를 호출하지 않았으므로 남자 코드의 지역 변수 balacne가 90이 되었다. 그리고 Context Switch가 일어난다.
  • 다음 여자 코드가 실행되면서 balance값이 또 90이 된다. put_balance()가 실행되면서 은행 서버에 잔고가 업데이트된다. 그리고 Context Switch가 일어난다.
  • 남자가 put_balance()를 호출하면 둘이 각각 10만 원을 인출했는데 은행 서버에는 잔고가 90만 원이 된다.

Context switch 때문에 문제가 생기는 것이다. balance라는 변수가 중요하다. 여기서 사용되는 로컬 변수 balance와 은행 서버의 글로벌 변수 balance가 있는데, 글로벌 변수를 공유하기에 문제가 발생하는 것이다.

Syncrhronization Problem

동기화 문제는 공유되는 자원에 동시에 접근할 때 생기는 문제이다. 쓰레드 뿐 아니라 프로세스에서도 일어날 수 있다. 그런 문제를 race condition이 발생했다고 얘기한다.

  • race condition
    • 프로세스나 쓰레드들이 공유되는 데이터에 동시에 접근해서 읽고 쓰고를 반복하는 상황이다.
    • 이 문제는 non-determinstic 하다. 확실히 문제가 발생하는 게 아니라, 문제가 생길 수도 있고 안 생길 수도 있다는 것이다. 문제가 생길 가능성은 타이밍에 달려있다.

이런 상황에서 적절한 메커니즘이 필요하다. 그래서 동기화를 했는지 안 했는지에 따라 이런 문제를 잘 막았는지 아닌지가 결정된다.

 

동기화는 모든 공유되는 자료 구조에서 필수적이다.

  • 버퍼, 큐, 리스트 등에서 문제가 생길 수 있다.

이 코드에서도 race condition이 발생한다. *count = *count + 1을 보면 사실 최소 세 개의 어셈블리어로 구분되고, 여기서 문제가 발생한다.

어떤 문제가 생길 수 있을까? 은행 예제와 같은 문제가 발생할 수 있다. 어셈블리어 코드 레벨에서 분석해 보자.


Citical Sections

쓰레드간의 스택은 따로 있기에 로컬 변수는 절대 공유되지 않는다. 일반적인 경우라면 포인터도 문제가 생기지 않을 것이다.

글로벌 변수동적 할당된 객체에서 문제가 생긴다. 동적 할당된 객체는 힙에 쌓이고 공유되기 때문이다. 꼭 쓰레드 뿐 아니라 프로세스에서도 shared-memory 메커니즘으로 문제가 생긴다.

결국 동기화의 문제는 어떤 데이터를 공유하고 있고, 동시에 접근해서 읽고 쓰고를 반복하면 생기는 것이다.

 

shared resources에 접근하는 코드의 조각을 Critical sections라 한다.

여기서 파란색 코드 세 줄이 critical sections이다.

 

mutual exclusion이 잘 지켜져야 동기화 문제가 생기지 않는다.

  • 암묵적인 상호 배제, 한 쓰레드만 critical sections에 들어가서 작업하는 것을 mutual exclusion이라 한다.
  • 한 쓰레드가 critical sections을 떠나야지만 다른 쓰레드가 critical sections에 들어올 수 있다.

critical sections이 있다는 것은 race conditions이 있다는 것이다.

 

동기화 문제를 잘 해결하려면 다음의 사항을 지켜야 한다.

Mutual exclusion

  • 크리티컬 섹션에는 최대 하나의 쓰레드만 존재해야 한다.

Progress

  • 크리티컬 섹션에 한 쓰레드가 들어갔으면, 그 쓰레드는 반드시 적절한 시간 내에 결과를 내고 나와야 한다.
  • 또, 크리티컬 섹션 밖에 있는 쓰레드는 다른 크리티컬 섹션에 들어가는 쓰레드를 방해해서는 안된다.

Bounded waiting

  • 크리티컬 섹션에서 한 명이 나오면 반드시 다른 사람이 들어가야 한다. 순서가 밀려서 늦게 들어갈 수는 있지만 언젠가는 들어가야 한다.

Performance

  • 크리티컬 섹션에 들어갔다 나오는 과정은 오버헤드가 발생하는데, 그 오버헤드를 최대한 줄여야 한다.

동기화 문제를 방지하는 방법은 4가지가 있다.

Locks

  • 굉장히 원초적이고 기본적인 방법이다.

Semaphores

  • 일반적으로 많이 사용한다. 여러 종류가 있는데 그들을 잘 알아둬야 한다.

Monitors

  • Locks, Semaphores를 이용해서 language 레벨에서 지원하는 방법이다.

Messages

  • 메시지를 주고받는 방법이다. 메시지를 잘 주고받아서 동기화를 맞춘다.