Thread/팁

[Thread] Thread Safe

Binceline 2013. 12. 28. 15:27

스레드 안전이란 스레드에서 사용해도 안전한 코드를 말한다.

스레드 안전이라는 것에는 더 세분화되어 재진입, 원자적 실행 등의 용어로 나눠서 쓰기도 한다.


우선 안전(Safe)하다는 것의 의미는

- 프로세스의 치명적인 중단을 일으키지 않음

- 기능을 제대로 수행함

이렇게 생각하면 된다.

즉 프로세스는 죽이지는 않지만 결과가 분명하지 않은 경우를 쓰레드 안전에 위배된다고 하는 것이다.

스레드 안전을 만족하는 함수를 TSF(Thread-Safe Function) 라고 한다. 스레드 안전은 보통 함수 단위를 말하는 경우가 많다.

스레드 안전을 이해하기 위해서는 우선 스레드에 안전하지 않은 코드가 어떤 것인지부터 알아야 한다. 스레드에 안전하지 않은 필요조건은 여러 개의 스레드가 해당 코드를 실행했을 때 예측할 수 없는 결과를 내놓을 가능성을 내포한 경우를 말한다.

여기서 예측할 수 없는 결과란 다음과 같다.

- 심각한 오류로 인해 중단되는 것

- 작동은 되지만 결과가 분명하지 않은 것


스레드 안전성을 지키지 못하는 코드의 예


 char* sum_strnum(const char* pS1, const char* pS2)

 {

static char buf_sum[16];

snprintf(buf_sum, sizeof(buf_sum), "%d", atoi(pS1) + atoi(pS2));

return buf_sum;

 }


이 코드는 문자열 2개를 숫자로 인식해서 그 결과를 다시 문자열로 리턴하는 함수이다.

여기서 stdio 함수들은 스레드 안전을 만족하기 때문에 괜찮다고 생각할지 모르지만 사실 그렇지 않다.


왜냐하면 buf_sum 변수를 정적으로 만들었기 때문이다. 복수의 스레드에서 이 함수에 접근한다면 그 결과는 알 수 없게 된다. 

예를 들어

t1, t2 스레드가 거의 동시에 접근했을 때


t1    ->    pS1 = "123", pS2 = "456"     이 인자로 들어오고

t2    ->    pS1 = "888", pS2 = "777"     이렇게 들어온 후


t1 스레드가 snprintf 함수에서 sum_buf에 데이터를 복사하는 도중에

t2 스레드가 접근한다면 이 결과는 이상하게 뒤섞여 알 수 없게 된다.


가로길이 : 함수 실행 시간

빨강 : 겹치는 부분

이렇게 함수 실행이 t1, t2 스레드 실행에서 겹치게 된다는 것이다.

이를 '스레드 안전에 위배된다' 고 한다.


하지만 만약 이 코드에서 겹치는 부분을 락을 건다면 어떨까?

문제는 이 코드가 근본적으로 문제를 가지고 있다는 것이다. 락을 건다면 함수 시간이 길어지므로


노랑 : wait 시간

이렇게 된다.

그래서 겹치는 부분은 발생하지 않겠지만 문제는 buf_sum이 공유 자원이라는 것이다. 기능은 제대로 동작하겠지만 원하는 결과가 나오지는 않을 것이다. 왜냐하면 t1 스레드에서 이 함수를 완료하고 곧바로 t2스레드에서 완료해버리면 t1 스레드에서 완료했던 그 결과값을 지워져 버릴 것이기 때문이다.

그래서 이것을 재진입성 함수 형태로 코드를 변경해야 한다.


재진입성이란 병렬 실행을 보장하도록 작성된 형태로 구성되어 스레드 안전성을 확보한 경우이다.

즉 각각의 스레드에서 호출한 작업은 절대적으로 독립적이라는 것이다.

이는 함수 내부에서 어떤 의존 관계도 포함하면 안 되므로 static 메모리나 공유 객체를 사용하지 않는 구조로 구성해야 한다.

즉 저 buf_sum 메모리를 이 함수 안에서 만들지 말고 외부에서 인자값으로 넘겨받는 형식으로 만들면 된다.

물론 TLS(Thread Local Storage)라는 기법을 사용하면 그럴 필요가 없다. 이는 같은 이름이라고 해도 스레드별로 다른 공간을 바라볼 수 있도록 하는 방법이다.

반응형