프로그래머들은 critical section을 락으로 둘러, 영역이 원자 단위 명령어인 것처럼 실행한다
=> 공유 자원 접근에서 인터럽트, 쓰레드의 자원 접근 실행 순서 꼬임 등으로 인해 결과가 다를 수 있따
변수
다사용가능 상태
(unlocked or free)사용 중 상태
(locked or held)lock()
임계영역
으로 진입한다unlock()
락은 스케줄링
에 대한 제어권을 제공한다
락으로 코드를 감싸 그 코드 내에서는 하나의 쓰레드만 동작하도록 보장할 수 있다
어느 정도 질서
를 부여한다
POSIX에서는 lock을 mutex라고 부른다
mutual exclusion
)을 제공하기 때문이다다른 변수들을 보호하기 위해 다른 락들을 사용할 수 있다
효율적인 락은 mutual exclusion을 낮은 비용으로 제공한다
락을 구현하기 위해선 하드웨어와 OS가 도와준다
락의 정상 동작 여부 평가를 위해 평가기준을 둬야한다
mutual exclusion
Fairness
쓰레드들이 락 획득에 대한 공정한 기회가 주어지는가?(starvation)
Performance
임계영역에서 인터럽트 비활성화
권한
이 있어야한다멀티프로세서
에선 지원이 안된다인터럽트 활성/비활성화를 사용하지 않고 락을 구현하기 위해선 하드웨어의 도움이 필요하다
소프트웨어만 사용하면 구현할 수 없다
아래로 살펴보자
작동 방식
flag 변수를 이용해 쓰레드가 락을 획득했는지 검사
임계영역에 진입하는 스레드가 lock을 호출해 플래그가 1인지 검사하고
1이 아니면 플래그를 1로 설정 후 해당 스레드가 락을 보유한다고 표시
임계영역에서 나오면 쓰레드가 unlock을 호출해 플래그 값을 초기화해 락을 보유하지 않는다 알림
만약 누가 임계영역을 사용 중 이라면 쓰레드는 spin wait을 하며 기다림
문제점
Correctness 제대로 작동해?
Performance
atomic한 인스트럭션을 위해 하드웨어의 지원이 필요하다
하드웨어 기법 중 기본은 test and set, atomic exchange
로 알려진 명령어이다
Test-and-Set operation
int TestAndSet(int* ptr, int new){
int old = *ptr; // 이전의 포인터 값을 가져옴
*ptr = new; // 새로운 값 저장
return old; // old 값 반환
}
ptr이 가리키던 예전 값을 반환하고 동시에 new에 새 값을 저장한다
원자적으로 실행이 된다
Test(load)와 Set(store)를 하나의 원자적 operation으로 만든다
old 값을 반환한다
즉시 new 값을 갱신한다
순서는 원자적
으로 실행된다
이를 활용해 spin lock
을 만들 수 있다
한 쓰레드가 락 오너로써 임계영역을 사용하고 있을 때
다른 쓰레드가 락을 획득하려고 하면
flag가 1이므로 계속 spin을 돌 것이다
만약 원래의 owner가 lock을 반환을 한다면 flag가 0으로 설정됨과 동시에
test and set 함수에서 flag값을 1로 바꾼디 해당 쓰레드가 락 owner가 된다
Preemptive scheduler
를 사용해야한다Correctness
Fairness
Performance
int CompareAndSwap(int* ptr, int expected, int new){
int actual = *ptr; // ptr에 예전 값을 업데이트
if(actual == expected)
*ptr = new; // 메모리 위치 업데이트
return actual; // 반환
}
void lock(lock* lock){
while(CompareAndSwap(&lock->flag,0,1)==1); // 스핀
}
주소의 값이 expected 변수와 일치하는지 검사한다
spin에서 flag가 0인지 검사하고 그렇다면 자동으로 1로바꾼다
void lock(lock* lock){
while(LoadLinked(&lock->flag || !StoreConditional(&lock->flag,1)));
}
락을 획득할 수 있는 상태가 되면 쓰레드는 store-conditional로 락 획득을 시도, 성공한다면 스레드는 flag를 1로 변경 후 임계 영역으로 진입
락이 사용가능하니 0을 반환했을 것
storeconditional로 flag를 1로 변경하려는 순간 인터럽트에 걸려
다른 쓰레드가 락을 차지
그럼 인터럽트 걸린 쓰레드는 락 획득을 못함!
왜? store 조건문에서 주소 값이 업데이트 안되었을 때만 flag를 1로 변경하니
이미 변경되었기 때문에 락을 못 얻는다
int FetchAndADD(int* ptr){
int old = *ptr;
*ptr = old +1;
return old;
}
방법
락을 하면 티켓값을 늘린다
언락을 하면 턴 값을 늘린다
티켓과 턴 값이 같아야 lock을 사용할 수 있다
같지 않으면 spin한다
쓰레드가 티켓 값을 할당 받으면 미래에 무조건 락을 사용할 수 있게 스케줄이 된다
티켓 0 turn 0
multual exclusion (corecteness)
Fairness
Performance
하드웨어 기반 스핀 락은 간단하고 제대로 동작한다
그러나 효율적이지 않을 경우도 있다
어떻게 spinnnig을 피할까?
OS의 도움이 필요
락이 해제되기를 기다리며 스핀을 할 때 자신에게 할당된 CPU를 다른 쓰레드에 양보한다
yield() 시스템 콜은 스레드 상태를 running에서 ready로 전환한다
context switch를 할 때 비용이 상당하다
starvation 문제가 일어날 수도 있다
어떤 쓰레드가 락에 들어가려고 하는지 추적하는 큐
두 단계의 락은 락이 곧 해제될 것 같으면 회전 대기가 더 유용하다는 것을 인식