스레드 안전(Thread-Safety)란?

Park JeaHyun·2022년 10월 19일
0

멀티 스레드 프로그래밍

멀티스레드 프로그래밍은 하나의 프로세스에서 여러 개의 스레드를 만들어 자원의 생성과 관리의 중복을 최소화하는 것이다.

  • 장점
    • 멀티 프로세스에 비해 메모리 자원소모가 줄어든다.
    • Heap 영역을 통해서 스레드 간의 통신이 가능하기 때문에 프로세스 간의 통신이 간단해진다.
    • 스레드의 컨텍스트 스위칭은 프로세스의 컨텍스트 스위칭보다 훨씬 빠르다.
  • 단점
    • 힙 영역에 있는 자원을 사용할 때 동기화를 해야한다.
    • 동기화를 위해서 락을 과도하게 사용하면 성능 저하가 발생할 수도 있다.
    • 하나의 스레드가 비정상적으로 동작하면 다른 스레드도 영향을 받아 종료하게 될 수도 있다.

스레드 안전(Thread-Safety)

  • 스레드 안전(Thread-Satety)란 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없는 것을 말한다.
  • 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 옳바르게 나오는 것을 말한다.
  • Thread-sate하다라는 의미는 두 개 이상의 스레드가 race condition에 들어가거나 같은 객체에 동시에 접근해도 연산 결과는 정합성이 보장될 수 있게 메모리 가시성이 확보된 상태이다.

스레드 안전(Thread-Safety)의 여부 판단 방법

  • 다음과 같이 세 가지를 통해 thread-safe한 상태인지 판단할 수 있다.
  1. 전역 변수나 힙, 파일과 같이 여러 스레드가 동시에 접근할 수 있는 자원을 사용하는가?
  2. 핸들과 포인터를 통한 데이터의 간접 접근이 가능한가?
  3. 부수 효과를 가져오는 코드가 있는가?

스레드 안전을 지키기 위한 4가지 방법

Mutual Exclusion (상호 배제)
Atomic Operation (원자 연산)
Thread-Local Storage (쓰레드 지역 저장소)
Re-Entrancy (재진입성)

1) Mutual Exclusion( 상호 배제 )

  • 공유 자원에 하나의 Thread만 접근할 수 있도록 세마포어 / 뮤텍스로 락을 통제하는 방법이다.
  • 일반적으로 많이 사용하는 방법이다.
  • 적용 예시
    • Python은 Thread Safe하게 메모리를 관리하지 않아 GIL(Global Interpreter Lock)을 통해 Thread Safe를 보장한다.
    • Python에서 threading.lock이 있는데 threading.lock을 acqurie하면 해당 쓰레드만 공유 데이터에 접근할 수 있고 lock을 release해야만 다른 쓰레드에서 공유 데이터에 접근할 수 있다.

2) Atomic Operation (원자 연산)

  • 공유 자원에 접근할 때는 원자 연산을 이용하거나 원자적으로 정의된 접근 방법을 사용함으로써 상호 배제를 구현할 수 있다.
  • Atomic
    • 공유 자원 변경에 필요한 연산을 원자적으로 분리한 뒤에 실제로 데이터의 변경이 이루어지는 시점에 Lock을 걸고, 데이터를 변경하는 시간 동안 다른 쓰레드의 접근이 불가능하도록 하는 방법이다.
  • 적용 예시
    • a += b의 경우 먼저 +연산을 한 뒤에 = 연산을 함으로, 원자적이라고 볼 수 없다.

3) Thread-Local Storage (스레드 지역 저장소)

  • 공유 자원의 사용을 최대한 줄이고 각가의 쓰레드에서만 접근 가능한 저장소들을 사용함으로써 동시 접근을 막는 방법이다.
  • 일반적으로 공유 상태를 피할 수 없을 때 사용하는 방식이며, 전역 변수 사용을 자제하라는 뜻으로 생각하면 된다.

4) Re-entrancy (재진입성)

  • 쓰레드 호출과 상관 없이 프로그램에 문제가 없도록 작성하는 방법이다.
  • 어떤 함수가 한 스레드에 의해 호출되어 실행 중이라면 다른 스레드가 그 함수를 호출하더라도 그 결과가 각각에게 옳바르게 주어져야 한다.
  • 쓰레드끼리 독립적으로 동작할 수 있도록 코드를 작성하는 것으로 생각하면 된다.

JAVA 프로그래밍에서 Thread-Safe하게 설계하는 방법

  • java.util.concurrent 패키지 하위의 클래스를 사용한다.
  • 인스턴스 변수를 두지 않는다.
  • Singleton 패턴을 사용한다.
  • 동기화 (Syncronized) 블럭에서 연산을 수행한다.

이때, 일반적으로 구현하는 Singleton Pattern은 Thread-Safe하지 않는다.

  1. 싱글톤 패턴
  • 싱글톤 패턴이란 애플리케이션이 시작되고 하나의 클래스 인스턴스를 보장해서 전역적인 접근점을 제공하는 패턴을 말한다.
  • 장점
    • 전역 변수를 사용하지 않기 때문에 디버깅이 쉽고, 네임 스페이스가 망가지는 일이 없다.
    • 유일하게 존재하는 인스턴스로의 접근으로 통제가 가능하며, 데이터 공유가 쉬워진다.
  • 단점
    • 싱글톤 인스턴스가 너무 많은 일을 하거나 너무 많은 데이터를 가질 경우 결합도가 높아진다.
    • 멀티스레드 환경에서 동기화 처리를 하지 않으면 여러 개가 생성될 수 있다.
  1. 멀티스레드 환경에서 안전한 싱글톤 인스턴스 만들기
  • 게으른 초기화(Syncronized 블록 사용)
    • 속도가 너무 느려서 권장 되지 않음
  • Double-Check Locking
    • if문으로 존재 여부 체크하고, Syncronized 블록 사용하는 방법
    • 어느정도 문제 해결은 가능하지만 완벽하지 않음
  • Holder에 의한 초기화
    • 클래스 안에서 클래스(Holder)를 두어 JVM Class Loader 매커니즘을 이용한 방법
    • 개발자가 직접 동기화 문제를 해결하기 보다는 JVM의 원자적인 특성을 이용해 초기화의 책임을 JVM으로 이동하는 방법
    • 일반적으로 Singleton을 이용하는 방법

Spring에서는 이런 싱글톤 패턴을 통해 Bean에 대한 관리를 하는데 너무 복잡해지는 것을 방지하기 위해, Singleton Registry인 Application Context를 가지고 있다.

0개의 댓글