* 스레드 동기화를 설명하고 있지만, 프로세스 동기화 경우에도 딱히 다르진 않습니다
대부분의 락은 CPU 도움이 필요하다
동기화(synchronization)는 여러 스레드들이 동일한 데이터를 쓰고자 할 때 데이터의 변화가 일관성 있게 진행될 수 있도록 만드는 것을 의미한다
동기화는 락(lock)을 통해 구현되며 대표적인 락들은 아래와 같다
spinlock mutex semaphore
이 세 종류의 락은 모두 CPU의 도움을 필요로 한다
오늘은 왜 CPU의 도움이 필요한지 설명드리겠다
👉 락이 어떻게 동기화에 사용되는지 설명하는 글 (이걸 이해해야 아래 내용을 이해할 수 있습니다)
스핀락 사용 예제로 설명하겠다
스핀락은 락을 쥘 때까지 반복해서 락을 쥐려고 시도하는 락이다
그래서 반복문에서 계속 확인을 하기 때문에 CPU를 계속 사용하게 된다는 특징이 있다
아래는 스핀락 방식으로 어떻게 critical section(임계 영역)을 구현되는지 나타낸다
volatile int lock = 0; // global 변수
void test() {
while (test_and_set(&lock) == 1); // 반복 시도, CPU 계속 사용
// critical section
lock = 0;
}
여기서 test_and_set() 함수는
파라미터로 전달받은 변수의 값을 1로 바꾸고, 바뀌기 전의 기존 값을 리턴하는 함수이다
이 친구가 오늘의 주인공이다
예제 코드에서 test_and_set()을 주목하자
프로세스 A는 두 개의 스레드 x와 y가 있다고 가정해 보자
스레드 x와 y가 동시에 test()를 호출했고 스레드 x가 간발의 차이로 test_and_set(&lock)을 먼저 호출했다면 아래와 같은 순서로 동작하게 된다
스레드 x | 스레드 y |
test() 호출 | test() 호출 |
test_and_set(&lock) 호출 => 0을 리턴, lock은 1로 바뀜 => while 루프 탈출 |
|
test_and_set(&lock) 호출 => 1을 리턴, lock은 1로 유지 => while 루프 탈출 못함 |
|
critical section 영역 실행 | 계속해서 test_and_set(&lock) 호출 , 탈출 못함 , 무한 반복 |
lock = 0 | ↓ |
test() 호출 종료 | test_and_set(&lock) 호출 => 0을 리턴, lock은 1로 바뀌어져 있음 => while 루프 탈출 |
critical section 영역 실행 | |
lock = 0 | |
test() 호출 종료 |
위 표에서 test_and_set(&lock)을 호출하는 부분을 파란색으로 표시했다
세 줄로 이루어진 이 동작을 하나의 셀 안에 표시한 이유는
이 부분이 원자적으로(atomic) 동작하기 때문이다
test_and_set() 원자적으로 동작해야 한다
test_and_set()은 원자적으로 동작해야만 하는데, 왜 그런지 test_and_set() 내부를 보면 이해할 수 있다
아래 test_and_set() 코드는
'실제로 이렇게 구현되어 있다'는 것이 아니라, 내부 동작을 코드로 설명하기 위해 작성한 것이다
int test_and_set(int* lockPtr) {
int oldLock = *lockPtr; // 기존 값 저장
*lockPtr = 1; // 1로 바꿈
return oldLock; // 기존 값 반환
}
만약에 test_and_set()이 원자적으로 동작하지 않는다면,
즉, 하나의 스레드가 test_and_set(&lock)을 호출해서 동작 중인데
중간에 다른 스레드가 동일한 lock 변수에 대해서 test_and_set(&lock)을 호출해서 동작하는 것이 가능하다면,
두 스레드에서 모두 0을 리턴하는 경우가 발생할 수 있고
그렇다면 critical section(임계 영역)에 두 개의 스레드가 모두 진입하게 되고,
결국 critical section의 중요한 속성인 mutual exclusion(상호 배제)이 깨지게 된다
더 이상 critical section은 critical section이 아니게 되고, 동기화는 정상적으로 동작하지 않는다
그렇기 때문에 test_and_set()은 반드시 원자적으로 동작해야 한다
한 스레드가 이미 test_and_set()을 호출 중이면, 다른 스레드는 그 호출이 끝날 때까지 기다렸다가 이어서 test_and_set()을 한다
즉, 다른 스레드가 중간에 개입하는 일이 없어야 한다
CPU와 test_and_set()
여기서 CPU가 아주 큰 역할을 한다
CPU는 '원자적'으로 동작하는 특별한 CPU 명령어(instruction) 몇 가지를 지원하는데
그중에 하나가 지금까지 우리가 살펴본 test_and_set()이다
정리하면
동기화에 사용되는 락들은 'CPU'에서 제공하는 '원자적인' 명령어를 사용해서 구현된다
그래야만 락으로 구현된 critical section이 mutual exclusion 할 수 있다
쉬운코드는 본질에 충실합니다
👉 " 스핀락, 뮤텍스, 세마포 설명 " 영상 보러가기