CS/OS

[OS] Thread Safe(스레드 안전)

어둠의 개발자 2023. 1. 11. 20:50

먼저 Thread(스레드)란  프로세스내에서 실행되는 여러 흐름의 단위이다.

 

하나의 프로세스내에서는 기본적으로 한 개 이상의 스레드를 가질 수 있는데, 두 개 이상의 스레드를 수행하는 것을 멀티 스레딩이라고 한다.

프로세스 내부의 스레드들은 각각 자신의 스택 영역을 독립적으로 가진 상태로 Code, Data, Heap 영역을 서로 공유하여 사용하기 때문에

두 개 이상의 쓰레드가 자신이 포함된 프로세스의 영역에 있는 함수를 호출할 수 있다. 그렇기 때문에 두 개 이상의 스레드들이 동시에 공유중인 프로세스의 영역(Code/Data/Heap)에 접근하게 되면 쓰레드가 충돌하는 동기화 문제가 발생할 수 있다.

 

공유 변수의 값이 50이라고 가정할 때

  • A 쓰레드가 접근하였을 때 해당 공유 변수의 값이 50 이상일 경우 50을 빼려고 한다. 이 때 50 이상인 것을 확인하였다.
  • 이 때 B 쓰레드로 제어권이 넘어가고 B 쓰레드가 해당 공유 변수를 0으로 설정하였다, 다시 A 쓰레드로 제어권이 넘어갔을 때
  • A 쓰레드는 이미 50이상인 것을 확인하였기 때문에 50을 빼는 행위를 한다. 공유 변수의 값이 -50이 되어버리는 상황이 발생하였다.

프로세스의 영역은 스레드들이 공유하여 사용할 수 있으므로 대체로 공유 자원이라고 한다.

 

스레드 안전(Thread Safe)이란 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 의미한다.

보다 엄밀하게는 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바르게 나오는 것으로 정의한다.

 

ex:) 100만원을 가지고 있는 통장에 접근 할 수 있는 두 개 이상의 출금 카드로 동시에 100만원을 인출하면 200만원이 인출되는 상황이 발생할 수 있으나 Thread Safe를 통해 100만원만 인출이 되고 다른 카드에서는 인출이 되지 않게 보장해주어야 한다.

 

Thread Safe를 보장해주는 방법.

Re entrancy(재 진입성)

  • 스레드, 동일한 스레드, 다른 스레드에 의해 실행되어도 원래 실행을 올바르게 완료할 수 있게 코드를 작성한다.
  • 정적, 전역 변수등의 상태 대신에 일반적으로 스택에 있는 각 실행에 대한 로컬 변수에 상태 정보를 저장한다.
  • 모든 non-local 상태는 원자적 연산을 통해 액세스해야 하며 데이터 구조도 재진입해야 한다.(?)

Thread-local storage(스레드 로컬 저장소)

  • TLS는 쓰레드가 각각 가지고 있는 저장소이다.
  • 이 곳에 저장되는 값들은 전역변수 이지만, 한 쓰레드 내에서만 유효한 전역 변수이다. 이러한 면에서 Stack과는 차이가 있다.
  • 공유하는 영역인 Heap, 데이터 영역의 값을 사용 할 만큼 큼직하게 잘라서 TLS로 가져온다. 이 과정만 하기 때문에 부하가 크지는 않을 것이다. 이렇게 공유영역에서 가져온 값을 TLS로 옮기면 그 이후에 mutex를 차지하기 위한 경합이 일어나지 않을 것이다.

Immutable objects(불변 객체)

  • 공유 자원을 사용한다면 불변 객체를 사용하라는 의미이다. 불변 객체는 변하지 않는 읽기 전용 자원이기 때문에 여러 스레드가 동시에 접근해도 스레드 안전성이 보장된다.
  • Java에서 대표적인 immutable 객체로 String 객체가 존재한다.
  • 만약 변경 가능한 객체를 사용한다면 공유 객체를 그대로 가져와서 사용하지 말고 새로운 객체를 생성하여 공유 자원 객체의 값들을 복사하여 사용하자.
  • 약간 DB에서 Entity를 그대로 들고 와서 사용하지 말고 DTO를 통해 사용하는 것과 비슷한 의미인 것 같다.

Mutual exclusion(상호 배제)

  • 공유 자원을 꼭 사용해야 할 경우 해당 자원의 접근을 세마포어 등의 락으로 통제한다.
  • Java의 synchronized를 사용하라는 의미이다.
  • synchronized 키워드를 사용하면 어떤 한순간에는 하나의 스레드 만이 임계 영역(Critical Section) 안에서 실행하는 것이 보장된다.

Atomic operations(원자성 작업)

  • 공유 자원에 접근할 때 원자 연산을 이용하거나 '원자적'으로 정의된 접근 방법을 사용함으로써 상호 배제를 구현할 수 있다.
  • 원자적 연산이란 중단되지 않는 연산을 의미하며 스레드가 절대 간섭할 수 없음이 보장된다.

 

Reference

https://velog.io/@onenewarm/Thread-Local-StorageTLS