8.1 공유 자원
•
주방에 오븐이 하나뿐이라면 오븐에서 칠면조를 굽는 동시에 오븐을 사용하는 다른 요리를 조리할 수가 없는데, 오븐이 바로 공유자원이다.
•
요리사가 여려 명 있다면 효율이 증가하겠지만, 더 많은 정보 교환과 조정이 필요하기 때문에 요리 과정이 어려워질 수 밖에 없다.
서로 실행되는 순서나 실행 환경에 상관없이 여러 작업이 접근해 사용해도 의도대로 동작하는 함수나 연산의 성질을 스레드 안전(thread safe)이라고 한다.
•
스레드 안전성을 확보하려면 애플리케이션을 잘 설계하는 것이 가장 중요하다.
8.2 경쟁 조건
•
여러개의 ATM에서 동시적으로 하나의 account에 읽기 및 쓰기를 실행했을 때, 경쟁조건(race condition; 여러 트랜잭션이 동시에 같은 자원에 접근하여 결과가 실행 순서에 따라 달라지는 상황)이 발생한다.
1.
ATM A가 공유 메모리에서 잔고 0을 읽음
2.
ATM B도 거의 동시에 공유 메모리에서 잔고 0을 읽음
3.
ATM A는 자신이 읽은 0에 10을 더해 10을 공유 메모리에 기록함
4.
ATM B도 자신이 읽은 0에 10을 더해 10을 공유 메모리에 다시 기록함
⇒ 손실된 갱신(Lost Update; 두 트랜잭션이 같은 값을 읽고 각각 수정 후 기록하여, 나중에 수행된 트랜잭션의 수정이 앞선 트랜잭션의 결과를 덮어쓰는 문제)이 일어남
◦
여러 작업이 동시에 연산을 수행하면서 부정확한 결과를 내지 않으려면 공유 자원에 대한 접근을 동기화해 스레드 안정성을 확보할 수단이 필요하다.
8.3 동기화
동기화(synchronization)는 여러 작업 간에 공유 자원에 대한 접근을 제어하는 수단이다.
•
동시에 접근을 허용해서는 안 되는 공유 자원을 여러 작업이 함께 접근해야 할 때 유용하다.
•
동기화를 적절히 적용하면 여러 작업이 상호 배제와 정확한 순서로 공유 자원에 접근하는 것이 보장된다.
프린터를 사용하는 프로세스는 인쇄를 모두 마칠 때까지 프린터를 제어해야하는데, 그렇지 않으면 서로 다른 프로세스가 인쇄한 내용이 번갈아가며 인쇄된다. 그래서 동시에 인쇄 연산을 수행하는 작업은 하나만 있을 수 있게 임계 구역(critical section) 안에서 상호 배제를 강제할 수단이 필요하다.
임계 구역을 보호하는 수단 중 가장 기본적인 것으로 락(lock)이 있다.
8.3.1 상호 배제, 뮤텍스
작업이 어떤 공유 자원을 사용하려면 먼저 해당 자원에 락을 걸어야한다. 다른 작업은 대기 상태(blocked state)로 기다린다.
⇒ 이러한 방식을, 상호 배제(mutual exclusion), 또는 뮤텍스(mutext)라고 한다.
동시에 공유 자원에 읽기/쓰기, 쓰기/쓰기 연산을 할 수 없게 된다.
8.3.2 세마포어
세마포어(semaphore) 또한 뮤텍스와 비슷하게 공유 자원에 접근을 제어하기 위한 동기화 수단이다. 그러나 세마포어는 두 개 이상의 작업이 세마포어에 락을 걸거나 해제할 수 있다.
주차장을 예시로 들었을 때
•
뮤텍스는 내부 변수인 주차된 차의 목록에 대한 접근을 통제하는 목적으로 쓰이고
•
세마포어는 주차 공간 수를 이용해 주차장 내 차량 수를 제한하는 메서드(enter, exit)의 실행이 교차하지 않도록 하는 목적으로 쓰인다.
8.3.3 원자적 연산
원자적(atomic)연산은 한 단계 만에 실행이 끝나는 강력한 연산을 사용해 불필요한 간섭의 가능성을 애초에 차단하는 방법이며, 원시 데이터 타입을 다루는 가장 단순한 형태의 동기화 기법이다.
•
원자적 = 해당 연산의 중간 단계를 다른 스레드가 엿볼 수 없다는 뜻
•
예를들어 add 0x9082a1b, $0x1(0x9082a1b 주소에 1을 더하라는 명령) 와 같은 어셈블리 명령이 있다면 아무 간섭을 받지 않고 명령을 수행한다.
•
이러한 연산을 활용하려면 특수한 기계어 인스트럭션과 하드웨어 수준의 원자적 성질을 소프트웨어 수준까지 끌어오는 하드웨어와 소프트웨어 간의 긴밀한 협조가 필요하다.