Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Thread safety

Thread safety는 멀티 스레드(:12) 프로그래밍에서 사용되는 용어로, 멀티 스레드 환경하에서 사용할 수 있도록 코드 조각이 - 즉 함수 - 만들어 졌다면, 그 코드 조각은 스레드 세이프하다고 말한다.

스레드 세이프는 멀티 스레드 프로그래밍 환경에서 매우 중요한 키워드다. 1990년 이전까지는 프로그래머들이 스레드 세이프에 대해서 민감하게 반응할 필요가 없었다. 멀티 스레드 환경이 그다지 일반적이지 않았기 때문이다. 그러나 윈도우 시스템이 멀티 스레드기능을 포함하고, BSD와 리눅스(:12)운영체제가 널리 확산되면서, 일반적으로 고려해야할 이슈가 되었다. 멀티 스레드 프로그램은 여러개의 스레드가 동시에 동일한 메모리 공간에서 실행된다. 그러므로 만약 데이터를 전역 메모리 공간 두는 함수를 여러 개의 스레드가 동시에 사용하게 되면, 잘못된 연산이 수행될 수 있다.

코드가 스레드 세이프한지 결정하는 것은 쉬운일이 아니다. 하지만 일반적인 측정방법은 있다.
  • 전역 변수나 heap(:12)을 사용하지 않아야 한다.
  • 전역 자원을 위한 할당/재할당/해재가 없어야 한다. file(:12), pipe(:12) 등등
  • 전역 변수의 주소를 반환해서는 안된다.
  • 핸들 혹은 포인터에 대한 간접 엑세스가 없어야 한다.

스레드 세이프의 달성

스레드 세이프한 코드를 만들기 위한 일반적인 방법은 다음과 같다.
  1. Re-entrancy : 재진입성 이라고 한다.
멀티 스레드 프로그램에서는 어떤 함수가 스레드 A에서 실행중에 스레드 B가 이 함수를 실행할 수 있다. 나중에 스레드 A가 다시 실행했을 때, 이전의 코드의 상태가 그대로 유지되면, 이를 재진입성이 보장된 함수라고 한다. 이를 위해서는 함수가 사용하는 변수가 스태틱이나 전역변수가 아닌 각각의 지역 변수 형태로 저장되어야 한다.
  1. 상호배제 혹은 프로세스 동기화
공유 자원을 여러 스레드가 엑세스 할때, 동시에 사용할 수 없게 한다. 일반적으로 임계영역을 둬서 한번에 하나의 스레드만 진입할 수 있도록 제어한다. 이 방식의 가장 큰 문제점은 제어가 복잡하다는 점이다. 이 방식은 race:::condition(:12), 데드락(:12), livelocks(:12), starvation(:12)과 같은 디버깅이 어려울 수 있는 미묘한 문제를 불러 일으킨다.
  1. Atomic operations : 연산의 원자화
공유 자원에 대한 엑세스를 다른 스레드가 인터럽트 하지 못하도록 연산을 원자화 한다.

재진입과 스레드 세이프

함수가 재진입 가능하다고 해도, 여전히 스레드 세이프 하지 않을 수 있다. 설정 정보를 읽기 위해서 설정파일을 여는 함수를 예로 들어보자.

예제

아래의 C(:12) 코드는 스레드 세이프 하다. 하지만 재진입성을 보장하지 않는다.
int function()
{
	mutex_lock();
	...
	function body
	...
	mutex_unlock();
}
위 코드에서, 여러 개의 다른 쓰레드가 함수를 호출하더라도 문제가 발생하지 않는다. 그러나 만약 이 function 함수가 재진입 가능한 인터럽트 핸들러에서 사용되고, function 함수내부를 실행중일 때 두번째 인터럽트가 도착한다면, 두번째 루틴은 hang 상태에 놓일 것이다. 인터럽트 서비스는 다른 인터럽트를 비활성화 시킬 수 있기 때문이다.

아래의 C 코드는 재진입성을 보장한다. 그러나 스레드 세이프하지는 않다.
int function()
{
	char *filename="/etc/config";
	FILE *config;
	if(file_exist(filename))
	{
		config=fopen(filename);
	}
}
위 함수는 완전한 재진입성을 보장한다. 어떤 경우에 호출하더라도 실패하지 않을 것이다. 그러나 이는 단일 스레드일 경우이다. 만약 멀티 스레드 환경에서 실행된다면, 다른 스레드가 설정파일을 제대로 다룰수 있을지를 보장할 수 없다.

POSIX 함수의 재 진입성 보장

POSIX 함수의 상당수는 멀티 스레드 환경을 가정하지 않고 만들어졌다. 때문에 POSIX(:12) C 함수를 사용할 때는 주의할 필요가 있다. strtok(:3)함수등은 재진입성을 보장하지 않는다. 이 경우 strtok_r 함수를 사용하면 된다. _r이 붙은 함수는 원형 함수가 재진입성을 보장하도록 재 작성되었음을 의미한다.

errno 와 thread safe

POSIX Thread는 기본적으로 errno에 대한 스레드 세이프를 보장한다. 다른 유닉스(:12) 운영체제로의 포팅을 염두에 둔다면, gcc 컴파일 옵션에 -D_REENTRANT (솔라리스), -D_THREAD_SAFE (AIX)를 명시한다.

관련 글