TIL

23.10.30

오잉머신 2023. 10. 30. 19:15

1. 스핀락, 뮤텍스, 세마포

동기화(synchronization)을 위한 여러 전략과 각각의 차이!
 

  • race condition : 여러 프로세스/스레드가 동시에 같은 데이터를 조작할 때, 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황
  • synchronization : 여러 프로세스/스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것
  • critical section : 공유 영역의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역
    • 하나의 프로세스/스레드만 진입 = mutual exclusion

📍 어떻게 mutual exclusion을 보장할 수 있을까?

락을 사용하자!

예제를 보자

여러 스레드가 lock 변수에 접근
while루프를 통해 lock 획득시도
lock 획득하면 cs 들어가서 할일 하고 다 하면 lock 반환
 
TestAndSet 함수
- lock의 원래 값을 반환
- 반환 직전에 lock을 1로 바꿈
 
근데 testAndSet의 첫번째 줄에 동시에 접근하면 결국 똑같은 문제 발생하는거 아냐?
비밀은 testAndSet에 있다.

cpu에서 지원하는 atomic 명령어
 
같은 메모리 영역에 대해 동시에 실행되지 않는다
= 같은 파라미터를 두개 이상의 스레드가 동시에 호출해도 cpu 레벨에서 알아서 먼저 하나를 실행시키고 그 다음거 실행
 
즉, 위 예제에서 어떤 스레드에서 TAS를 실행시키고 있다가 중간에 context switching이 일어나서 다른 스레드가 실행될 일 없음
만약 멀티코어 환경에서 두개의 스레드가 TAS를 동시에 실행시켜도 cpu 레벨에서 알아서 하나 실행시키고 그 다음거 실행
 
참고) TAS 바디 부분이 실제 동작은 아님. 그냥 설명용
 

📍 스핀락

락을 확인하는 과정자체가 cpu 먹음
 

📍 뮤텍스

락이 준비되면 깨워~

value를 취득해야만 critical section 진입 가능!
 
lock()
value를 누군가 갖고 있다면, 다음에 내 차례되면 깨워줘라는 의미로 큐에 넣어둠
value를 취득할 수 있다면 0으로 바꾸고 할일
 
unlock()
큐에 뭐 있으면 걔 깨우고
아니면 value를 다시 1로
 
guard는?
value자체도 사실 여러 스레드가 동시에 접근할 수 있음(공유 데이터)
value값 바꾸는 것도 critical section안에서 안전하게 보호받으며 바꿔줘야함
그렇지 않으면 race condition이 발생할 수 있다
=> guard : value값을 critical section안에서 안전하게 바꿔주기 위한 장치
 
정리
1. 락이 준비되면 깨워줘. 큐에 들어가있을게. -> 불필요한 cpu 낭비 최소화
2. cpu레벨에서 지원하는 atomic 명령어 사용 (TAS)
 

context switching?
mutex 경우 락을 취득할 수 없으면 잠들어있다가 깨워짐
 바로 그 잠들고 깨는 과정에 context switching 발생
spin lock은 내가 락을 취득할 수 있는지 없는지 계속 확인
 
멀티 코어?
단일 코어면 spin lock이어도 락을 취득하려면 누군가가 락을 풀어줘야하기 때문에 결국 context switching 필요 -> 스핀락 이점x
멀티 코어면 다른 스레드가 락 푸는 순간 context switching없이 바로 취득가능
 

📍 세마포

mutex랑 거의 유사

  • value
    • mutex: value = 1
    • semaphore : value가 여러 값을 가질 수 있다 (ex. 0,1,2,3...)
  • value 변환
    • mutex: value = 0, value = 1
    • semaphore : value -= 1, value += 1
      • 왜? critical section에 thread가 하나이상 들어갈 수 있게 하려고~

value = 1 이면 이진 세마포 (바이너리 세마포)
아니면 카운팅 세마포
 
시그널 매카니즘?

task3은, 반드시 task1 작업이 끝난 뒤에 작업
 
p1이 먼저 signal 호출 -> value = 1 -> p2가 wait 호출 -> task3 수행
p2이 먼저 wait 호출 -> value가 0이라 자기 자신을 큐에 넣고 기다림 ->  p1이 signal 호출 -> 잠자던 p2를 깨움 -> task3 수행 
 
=> semaphore는 순서를 정해줄 수 있음! (시그널 매커니즘을 가진다) 
 
그리고 wait와 signal이 같은 프로세스(또는 스레드) 안에서 실행될 필요 없음
 

 

1.
세마포는 wait하는애랑 signal하는 애랑 다를 수 있다. 
뮤텍스는 lock한 애만 unlock 가능 (lock 가진 스레드나 프로세스에 소속됨) -> 누가 unlock할지 예상 가능
 
2.
여러 프로세스나 스레드가 동시에 실행하면 cpu에서 context switching이 발생해 누구를 먼저 실행시킬지 결정 = 스케줄링
 
스케줄링 하는 방식 중 '우선 순위 높은 스레드/프로세스 먼저 실행' 방식의 경우
이때 같은 자원에 대해 경합해야할 때(락을 취득해야할 때) 문제 발생!

p2가 lock을 쥐고 있기 때문에 p1은 기다려야함 -> p1은 p2에 의존성 가짐 -> p1은 우선순위가 높음에도 불구하고 p2가 락반환할때까지 기다려야함
우선순위 기준 스케줄링이기 때문에 p2는 우선순위 낮아서 critical section에서 할일 다 하는데에 오래걸림 -> 그만큼 p1은 계속 기다려야함
 
 이 문제를 mutex에서 어떻게 해결?
뮤텍스는 lock을 가진 자만이 lock해제 가능.
p2의 우선 순위를 p1만큼 올려버림 -> 스케줄러가 순서 정할 때 (p1만큼 우선순위 높아진) p2실행 -> p2가 빨리 critical section에서 벗어날 수 있음
 
priority inheritance : p2의 우선순위를 p1만큼 올려주는 것
세마포는 이 속성 없다. 왜? 세마포는 누가 해제할지(signal을 날릴지) 모르기 때문에 
 

 

📍 댓글

  • 스핀락은 계속에서 CPU를 점유하는 형태로 동작하는 락이기 때문에 위험성이 있고
    그래서 보통의 경우에는 스핀락이 아니라 뮤텍스 락을 사용합니다.
    왜냐하면 CPU를 계속 점유한다는 것은 리소스 낭비를 의미하고 전체 애플리케이션 퍼포먼스에도 안좋은 영향을 줄 수도 있기 때문이죠