그럼 이제 동기화를 위한 여러 전략과 각각의 차이를 알아보자
먼저, 앞에서 배운 개념을 잠깐 짚고 넘어가자.
경쟁 상태 ( Race Condition )
여러 프로세스/스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황
동기화 ( Synchronization )
여러 프로세스/스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것
임계 영역 ( Critical Section )
공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역
임계영역에서 이 하나의 프로세스/ 스레드만 진입해서 실행한다는 의미가 바로
Mutual exclusion
Mutual exclusion을 보장 하는 법
lock을 사용하면 됨
프로세스 / 스레드가 lock을 획득하기위해 경합을 하고 그 중 성공한 프로세스 /스레드가 임계영역에 들어가서 실행하고, 임계영역에서 자신이 할일을 마칠 경우 다시 lock을 반환
예를 들어보자
간단한 함수 설명
쓰레드 ( T1, T2 ) 가 존재한다고 하고 함수를 보자
1. T1이 먼저 시작하고 lock이 0이므로 임계영역안으로 들어와서 실행됨
2. T1이 임계영역에 들어오면서 lock이 1로 바뀜
3. 그리고 T2가 시작되고 lock이 1이기 때문에 임계영역안으로 진입할 수 없음
4. T1이 자기 할일을 마치고 lock을 반환하면서 0으로 바뀜
5. T2가 이제 lock 0값을 가지고 while 루프 탈출하고 임계영역안으로 진입
6. 위 과정이 반복
T1과 T2가 임계영역안에서 동시에 실행되지 못함을 확인
하지만 물론 T1과 T2가 동시에 test and set을 호출하고 동시에 lockPtr을 실행하게되면 동시에 임계영역으로 들어올 수 있지 않을까? 하고 생각할 수 있다.
test and set이라는 함수는 CPU의 도움을 받음
CPU의 atomic 명령어
- 실행 중간에 간섭받거나 중단되지 않음
- 같은 메모리 영역에 대해 동시에 실행되지 않음
=> 절대 동시에 실행될 수 없게함 ( test and set의 body는 그냥 예시임 )
그래서 우리가 우려했던 그런일은 일어나지 않음
while에서 lock 체킹
예제에서 while 루프를 빠져나갈때 까지 lock을 확인하면서 lock을 가질 수 있을 때 까지반복 됨 이를 스핀락이라고 함
락을 가질 수 있을때 까지 반복해서 시도
하지만 이 방식은 lock을 기다리는 동안 CPU를 낭비한다는 단점 존재
ㅈ댐을 감지...
lock이 준비가 되면 자신을 깨워달라는 방식이 등장
lock을 가질 수 있을 때 까지 휴식
예제로 확인해 보자
value
먼저 마찬가지로 임계영역으로 들어가기 위해 lock 쥐기위한 경합을 하는 과정은 같음
위 함수에서 임계영역에서 동작을 하기 위해서는 value라는 값을 1을 가져야만 동작 가능
lock메서드를 먼저 살펴보면 만약 value를 누군가가 취득하고 있다면 다음에 자신의 차례가 되어서 lock을 취득할 수 있게 되면 깨워달라는 큐에 자기 자신을 넣게 되고 else에서 value를 획득할 수 있으면 그 value값을 쥐고 value값을 0 으로 바꿈
unlock 메서드도 간단하게 보면 lock 해제할때 큐에 하나라도 대기중인게 있으면 깨우고 아닐경우 value를 1로 바꿈
value값을 통한 임계영역에서 단 하나의 프로세스/스레드 가 실행될 수 있는 장치
guard
value라는 값 자체도 여러 스레드가 접근 하는 공유되는 데이터이기 때문에
임계영역 안에서 안전하게 보호 받으면서 그 값을 바꿔야 함. ( race condition 위험)
=> 안전하게 보호하기 위한 장치가 guard
guard의 사용예를 보자
value값을 바꿔주기 전에 guard를 취득하기위해 서로 경합을 하게되고 그 중 한개의 프로세스/스레드가 임계 영역안에 하나만 들어오게 될 경우에만 value값을 바꾸는 로직 실행 후 다시 guard를 0으로 바꿈 ( 위에 스핀락과 비슷한 맥락 )
간단하게 정리
- 큐에 들어가서 대기 하고 있을게( lock에서 큐에 넣음 ) 깨워줘(unlock에서 깨워줘) ! => cpu 낭비 최소화
- test and set -> atomic 명령어 사용
뮤텍스가 스핀락 보다 좋을까??
멀티 코어 환경에서 임계 영역에서의 작업이 컨텍스트 스위칭보다 빨리 끝나면 스핀락이 뮤텍스보다 이점이 있음
- 싱글 코어에서 작동을 할때는 무조건 cs가 일어나지만 멀티 코어에서 t1 t2가 각각 한개의 코어에서 작동을 할때는 cs가 일어나지 않기 때문에 스핀락이 이득
signal mechanism을 가진, 하나 이상의 프로세스/스레드가 임계 영역에 접근 가능하도록 하는 장치
뮤텍스와 거의 유사하므로 다른부분만 보고 넘어가보자
- 메서드 이름이 바뀜
- value가 여러 값을 가질 수 있음 ( mutex는 1 or 0 )
- wait 메서드에서 value를 차감하는 로직으로 바뀜
- signal 메서드에서 value를 더하는 로직으로 바뀜
왜 이렇게 바꿨을까?
임계영역에 하나 이상의 프로세스/스레드를 들어갈 수 있게 하기 위함
=> 가게 (임계 영역) 안에 좌석 (value)이 3개라면 3명(스레드)이 모두 사용할 수 있음
물론 세마포도 mutual exclusion을 보장하게 만들수 있다-> value를 1을 가지게 하면됨
그리고 이렇게 value를 1을 가질때
이진 세마포 ( Binary Semaphore )라고 함 ( 1이 아닐때는 카운팅 세마포 )
세마포의 특별한 기능
- 앞서 설명한 세마포의 signal mechanism 의미 = 순서를 정해줄 때 사용
- 반드시 signal과 wait가 같은 프로세스/스레드 안에서 실행될 필요가 없음
뮤텍스와 이진 세마포는 같은것이 아닌가?..
둘은 같아보이지만 같지 않다
- 뮤텍스는 락을 가진 자만 락을 해제 할 수 있지만 세마포는 그렇지 않음
=> 이진 세마포는 signal과 wait를 날리는 존재가 다를 수 있음- 뮤텍스는 priority inheritance 속성을 가짐 ( 세마포는 없는 속성 )
=> 여러 프로세스/스레드가 동시에 실행될 경우 CPU에서 CS가 발생하여 누구를 먼저 실행시킬지를 정해야함 이를 스케쥴링이라고 함
스케쥴링의 방식에는 여러가지가 있는데 그중 하나가 프로세스/스레드의 우선 순위에 따라서 더 높은 우선순위를 가진쪽을 실행시키는 방법이 있다. 하지만 위의 경우에는 같은 자원에 대해 경합을 하면서 서로 lock을 취득하려고 할때 생기는 현상이 있음
프로세스로 예를 들어보자
프로세스들 (P1 , P2) 이 있고 P1의 우선순위가 더 높다고 가정
1. P2가 먼저 진행을 하고 lock을 쥔 상태에서 임계 영역안에서 동작을 하고 있음
2. 그러다가 스케쥴링이 되어서 P1이 실행 되다가 lock 쥐려고 하지만 lock은 지금 P2가 사용하고 있기때문에 P1은 더이상 진행할 수 없음
=> 이떄부터 P1은 우선순위가 낮은 P2에 의존성을 갖게 됨 ( 우선순위가 높음에도 불구하고 아무 작업도 못함 )
뮤텍스에서는 이문제를 해결하는 방법으로
P2의 우선순위를 P1만큼 올려버리고 스케쥴링을 할때 P2의 우선순위가 높은걸로 보고 P2를 먼저 실행시키고 임계영역에서 빨리 빠져나오게 함
정리
상호 배제만 필요하다면 뮤텍스 , 작업 간의 실행 순서 동기화가 필요하면 세마포
디테일한 부분은 OS나 언어에 따라 다를 수 있으니 더 공부해야함.. 이게 기초라니;;..