seungjun.dev

레이스 컨디션을 해결하기 위한 동기화 기법 정리

레이스 컨디션을 해결하기 위한 동기화 기법들 세 가지 정리

레이스 컨디션을 해결하기 위한 핵심 열쇠

동기화란?

  • 질서를 부여하는 것

공유 자원에 접근하는 코드 영역(임계 구역)에 대한 동시 접근을 막고, 정해진 순서나 규칙에 따라 접근하도록 제어하는 모든 메커니즘을 의미한다.

쉽게 말하자면, 여러 스레드가 공유 자원이라는 '좁은 길'에 한꺼번에 뛰어들어 서로 부딪히고 엉키는 것이 레이스 컨디션이라면, 동기화는 이 길 앞에 신호등이나 안내요원을 두어 한 번에 하나의 스레드만 안전하게 지나가도록 교통 정리를 해주는 것이다.

동기화 기법

뮤텍스

  • 가장 널리 쓰이는 기본적인 잠금 메커니즘
  • 상호 배제(Mutual Exclusion)의 줄임말로, 이름 그대로 '너 아니면 나' 방식을 강제한다.

💬 비유: 칸이 하나뿐인 공용 화장실을 생각해본다. 이 화장실을 쓰려면 반드시 하나뿐인 열쇠가 필요하다.

  1. 잠금 획득: 스레드 A가 화장실을 쓰고 싶으면, 먼저 열쇠를 가져간다. (뮤텍스 획득)
  2. 임계 구역 실행: 열쇠를 가진 스레드 A만 화장실(공유 자원)에 들어가 볼 일을 본다. (이때, 다른 스레드들이 오더라도 열쇠가 없으므로 기다려야 한다.)
  3. 잠금 해제: 스레드 A가 용무를 마치고 나오면, 반드시 열쇠를 제자리에 반납한다. (뮤텍스 해제)
  4. 기다리던 다른 스레드가 열쇠를 가져가 같은 과정을 반복한다.

이처럼 뮤텍스는 임계 구역을 오직 하나의 스레드만 실행하도록 보장하는 방법이다.

세마포어

뮤텍스가 '오직 하나'만 허용하는 엄격한 규칙이라면, 세마포어는 '정해진 개수만큼' 허용하는 좀 더 유연한 규칙이다.

💬 비유: 5개의 좌석이 있는 스터디 카페를 생각해본다. 입구에는 남은 좌석을 표시하는 카운터(세마포어)가 있다.

  1. 자원 획득: 스레드가 스터디룸에 들어가려면 카운터를 1 감소시킨다. (남은 좌석 4개)
  2. 동시 사용: 최대 5개의 스레드까지는 동시에 들어와서 공부(자원 사용)할 수 있다.
  3. 대기: 카운터가 0이 되면(남은 좌석 0개), 새로 들어오려는 스레드는 자리가 날 때까지 밖에서 기다려야 한다.
  4. 자원 반납: 공부를 마친 스레드가 나가면서 카운터를 1 증가시킨다. (남은 좌석 1개) 그러면 밖에서 기다리던 스레드 중 하나가 들어올 수 있다.

이처럼 동시에 접근 가능한 스레드의 개수를 조절할 때 유용하다.

허용 개수를 1로 설정한 세마포어는 뮤텍스와 동일하게 동작한다

원자적 연산

  • 일단 시작되면 누구도 중간에 방해할 수 없고, 끝까지 완료되는 하나의 동작 즉, 핵심은 누구도 방해할 수 없이 '한 방'에 끝내는 거다.

💬 비유: 연필 vs 도장 어떤 문서에 숫자 10을 적어야 하는 상황을 가정한다.

  1. 일반적인 연산 (연필)

  • 먼저 1을 쓴다

  • 그 옆에 0을 쓴다 이 작업은 두 단계로 나뉘어있다. 그래서 문제가 생길 수 있다. 내가 1을 쓰고 0을 쓰기 전에, 다른 사람이 와서 그 옆에 5라고 써버리면 결과는 15가 된다. 내가 하려던 작업 중간에 다른 사람이 끼어든 것이다.

  1. 원자적 연산 (도장)

  • 10이 새겨진 도장을 종이에 찍는다
  • 이러면 누구도 중간에 끼어들 틈이 없다
  • 즉, 도장을 찍는 행위는 더 이상 쪼갤 수 없는(원자) 그냥 하나의 완전한 동작이다.

그래서 여러 스레드가 하나의 변수를 동시에 수정하려고 하기 때문에, 중간에 방해할 수 없도록 안전한 방식을 사용하는 것이다.