Thread Safe

송윤재·2024년 12월 4일

❓Thread Safe

멀티 스레드 환경에서 변수, 객체나 함수 등의 자원이 여러 스레드에 의해 동시에 접근되어도 프로그램이 정상적으로 동작하는 상태를 Thread Safe하다고 말한다.

  • Thread Safe하지 않은 경우

    public class NoThreadSafeClass {
    
        private int result = 0;
    
        public int getResult() {
            for (int i = 0; i< 100 ; i++){
                result = result + 1;
            }
            return result;
        }
    }
    • 싱글 스레드 환경에서는 문제가 없지만, 멀티 스레드 환경에서는 전역 변수를 공유하기 때문에 문제가 발생 할 수 있다.(ex. 한 스레드가 연산 중에 또다른 스레드가 result값을 읽어와서 연산하는 경우)
  • Thread Safe한 경우

    public class ThreadSafeClass {
    
      private int result = 0;
    
      public synchronized int getResult() {
          for (int i = 0; i< 100 ; i++){
              result = result + 1;
          }
          return result;
       }
    }
    • synchronized를 써서 동시에 result 값을 증가시키지 못하게 하는, 스레드 세이프한 환경이다.
    • 자바에서는 스레드 세이프한 환경을 구현하기 위해 임계 영역에 접근하는 메서드를 synchronized 키워드를 사용하여 동기화시킨다. Synchronized를 사용하면 해당 메서드를 수행할 때, 다른 스레드가 간섭하지 못한다.

즉 어떤 함수가 한 스레드에서 호출돼서 실행중일 때, 다른 스레드가 그 함수를 호출해서 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 서로 간섭하지 않고 정상적으로 나오는 상태를 스레드 세이프라고 한다.

❓Thread Safe를 지키기 위한 방법

  • Re-entrancy (재진입성)

    • 어떤 함수에 여러 스레드가 동시에 접근해도 언제나 같은 실행 결과를 보장하게 한다.
    • 약간 추상적인 개념, 코드 작성시 스레드끼리 독립적일 수 있게 구현하는 것을 뜻함.
  • Thread-local storage

    • 공유 자원의 사용을 최대한 줄여 스레드들의 동시 접근을 막고,
    • 각각의 쓰레드에서만 접근 가능한 저장소들을 사용함으로써 동시 접근을 막는 방법.
  • Mutual exclusion (상호 배제)

  • Atomic operations

    • 공유 자원에 접근할 때 atomic한 연산으로 구현한다.
    • “+=” 은, ‘+’ 연산 후 ‘=’ 연산을 하기에 원자적이지 않다.
  • Immutable Object

    • 공유되는 데이터라도 항상 똑같은 값이 나올 수 있도록 변경 불가능한 데이터 유형(자바에서 final 등)을 사용한다.

❓Peterson's Algorithm

Peterson's Algorithm은 1981년 수학자인 개리 피터슨이라는 사람이 고안한 알고리즘이다. 두 개 이상의 프로세스의 동기화(Synchronization) 문제를 해결하는 방법 중 하나

※ Critical-section problem 의 예전 버전 해결 방법이다. 예전 버전이므로 현대 버전에 적용되는 것이 보장 되지 않지만 알고리즘 자체가 동기화 문제 해결 방법 들의 기반이 된다.

여러 개의 프로세스가 있을 때, i번째 프로세스의 Peterson 알고리즘 구조도

    // flag: 신호, 공유자원을 사용하고 싶다라고 표현하기 위한 변수, 임계구역에 들어갈 때는 true, 나올 때는 false로 설정
    // turn: 차례, 누구 차례인지를 명시하는 변수, turn = 0 이면, 0번째 프로세스가 임계 구역에 들어가고, 1번째 프로세스가 기다린다.
    boolean flag[2];
    int turn;

    while(true) {
        // i번째 프로세스가 공유자원을 사용하고 싶다는 신호를 전달하기 위해 flag를 true로 바꿔준다.
        flag[i] = true;

        // i 번째 프로세스가 쓰기 전에 먼저 쓰고 싶어했던 프로세스가 있는지 확인하고 수행시켜주는 코드
        // 예를 들어 j번째 프로세스가 이 공유 자원을 쓰고 싶어 했으면 i번째 프로세스는 j번째 프로세스에 차례를 양보한다.
        turn = j;

        // busy waits 상태. turn이 j일 경우, 내 차례가 아니고 j가 자원까지 쓰고 싶어하면 나는 spinlock에 머무른다.
        // 이제 내 차례거나 j가 자원을 쓰고 싶지 않은 경우(!flag[j]), spinlock을 빠져나와 임계 구역에 진입할 수 있다.
        // turn과 flag를 통해 동시 접근을 막는다.
        while(flag[j] && turn == j);

        // critical section
        // 임계 구역에 진입해서 작업을 한다.

        flag[i] = false; // 다 썼으면 flag를 false로 바꿔준다.

        // remainder section
    }

👍임계구역 해결조건 3가지 만족

  • Mutual Exclusion(상호 배제)
    • 각각의 프로세스는 공유 자원에 접근하기 위해 나 외에 다른 프로세스가 기다리지 말거나 자신의 차례여야 한다.
    • 다시 말해서, 자기 순서가 아니면 아무도 접근하지 못한다.
    • Peterson 알고리즘에서 turn 변수를 이용하는데, turn이 i와 동시에 j일 수는 없으니 1번 조건은 만족한다.
  • Progress(진행)
    • 내 순서가 아니라도 남들이 쓰지 않으면 공유 자원을 쓸 수 있다.
    • Peterson 알고리즘에서 i번째 프로세스는 실행 완료 이후 flag[i]를 false로 만들기 때문에 2번째 조건 역시 만족한다.
  • Bounded-waiting requirement(한정 대기)
    • 자원을 요청하는 프로세스가 영원히 대기하지 않게 하는 것
    • flag[j]가 0이 되면서 적어도 한번은 i번째 프로세스가 실행할 수 있게 기회를 주는 것이기 때문에 역시 3번째 조건도 만족한다.

👎문제점

코드의 순서가 바뀌게 된다면 critical section problem이 발생할 수 있다.

❓Race Conditon

공유 자원에 대해 여러 개의 프로세스(스레드)가 동시에 접근을 시도할 때 접근의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태

제어 문제

  • Mutual Exclusion(상호배제)
    • 두 개 이상의 프로세스가 공용 데이터에 동시에 접근하는 것을 막아야 한다.
    • 다른 프로세스가 그 자원을 사용하지 못하면 문제를 피할 수 있다.
  • Deadlock(교착상태)
    • 상호배제를 시행하면, 추가적으로 발생하는 제어 문제
    • 두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
  • Starvation(기아상태)
    • 특정 프로세스의 우선 순위가 낮아서 원하는 자원을 계속 할당받지 못하는 상태를 말함.
    • 시스템 자원에 대한 경쟁, 프로세스 간의 통신 과정에도 발생 할 수 있는 문제

예방 방법

Semaphore(세마포어)

멀티 프로그래밍 환경에서 공유된 자원에 대한 접근을 제한하는 방법

  • 공유자원의 상태를 나타낼 수 있는 카운터로 생각할 때,
    • 사용하고 있는 스레드/프로세스의 수를 공통으로 관리하는 하나의 값을 이용해 상호배제를 달성한다.
    • 운영체제 또는 커널의 한 지정된 저장장치 내의 값
    • 일반적으로 비교적 긴 시간을 확보하는 리소스에 대한 이용
    • 유닉스 프로그래밍에서 세마포어는 운영체제의 리소스를 경쟁적으로 사용하는 다중 프로세스에서 행동을 조정하거나 또는 동기화하는 기술

Mutex(뮤텍스)

동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘

  • 임계구역을 가진 스레드들의 실행시간이 서로 겹치지 않고 각각 단독으로 실행(상호배제)되도록 하는 기술
  • 한 프로세스에 의해 소유될 수 있는 Key를 기반으로 한 상호배제 기법
    • Key에 해당하는 어떤 객체(Object)가 있으며, 이 객체를 소유한 스레드/프로세스만이 공유자원에 접근할 수 있다.
  • 다중 프로세스들의 공유 리소스에 대한 접근을 조율하기 위해 동기화(Synchronization) 또는 락(Lock)을 사용.
  • 즉, 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다.

❓반드시 락을 사용해야 할까?

스레드 세이프(Thread-Safe)를 보장하기 위해 반드시 락(lock)을 사용해야 하는 것은 아닙니다. 락은 일반적으로 가장 직관적이고 널리 사용되는 방법이지만, 성능 문제나 특정 요구 사항에 따라 락을 사용하지 않는 다른 방법들도 존재합니다.

  • 불변성(Immutable State) 사용
    • 공유 데이터를 불변 객체로 설계하면 스레드가 동시에 접근하더라도 데이터가 변경되지 않아 안전합니다.
    • 예: String(Java), tuple(Python) 같은 불변 객체.
  • 원자적 연산(Atomic Operations)
    • CPU가 제공하는 원자적 명령(atomic instructions)을 사용해 데이터의 동시 수정 문제를 방지할 수 있습니다.
    • 예:
      • C++: std::atomic
      • Java: AtomicInteger, AtomicBoolean
      • Python: atomic 라이브러리
    • 원자적 연산은 일반적으로 락보다 성능이 뛰어납니다.
  • 스레드 로컬 스토리지(Thread Local Storage)
    • 각 스레드가 별도의 데이터를 유지하도록 하여 공유 자원 접근을 회피합니다.
    • 예: ThreadLocal(Java), threading.local(Python)
  • 불변성 + 복사본 사용(Copy-on-Write, COW)
    • 데이터를 수정할 때 기존 데이터를 복사한 새로운 버전을 생성합니다.
    • 예: CopyOnWriteArrayList(Java), CowArrayList(C++)
    • 읽기 작업이 많은 경우 적합하지만, 쓰기 작업이 많으면 비효율적일 수 있습니다.
  • 락 프리 알고리즘(Lock-Free Algorithms)
    • 락을 사용하지 않고 데이터 구조의 무결성을 보장하는 알고리즘.
    • CAS(Compare-And-Swap)와 같은 원자적 연산을 활용.
    • 락 프리 큐, 스택 등 특정 데이터 구조에서 활용.
    • 예: Java의 ConcurrentLinkedQueue.
  • 메시지 전달(Message Passing)
    • 공유 데이터를 피하고, 스레드 간 데이터를 교환할 때 메시지 큐를 사용하는 방식.
    • 예: akka(Java/Scala Actor 모델), Go의 채널(Channel).
  • 함수형 프로그래밍
    • 상태 변경 없이 순수 함수와 불변 데이터를 사용하는 방식.
    • 스레드 간 동시 접근 문제가 없으므로 스레드 세이프가 자연히 보장됩니다.

출처

https://velog.io/@sooyoungh/Thread-safe%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%84%B8%EC%9D%B4%ED%94%84%EB%9E%80
https://worthpreading.tistory.com/90
https://wooono.tistory.com/523
https://wookkingkim.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Petersons-Algorithm%ED%94%BC%ED%84%B0%EC%8A%A8%EC%9D%98-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98
https://8156217.tistory.com/40
https://m.blog.naver.com/365blackstar/223367838123
https://velog.io/@yarogono/CS-Race-condition%EC%9D%B4%EB%9E%80
https://chelseashin.tistory.com/40

profile
CS 공부를 해봅시다

0개의 댓글