스레드 세이프 vs 재진입


89

최근에 "Malloc 스레드가 안전한가요?" 라는 제목으로 질문을했습니다. , 그리고 그 안에서 "malloc이 재진입 할 ​​수 있습니까?"라고 물었습니다.

나는 모든 재진입이 스레드로부터 안전하다는 인상을 받았습니다.

이 가정이 잘못 되었습니까?

답변:


42

재진입 함수는 C 라이브러리 헤더에 노출 된 전역 변수에 의존하지 않습니다. 예를 들어 C에서 strtok () 대 strtok_r ()을 사용합니다.

일부 함수는 '진행중인 작업'을 저장할 장소가 필요합니다. 재진입 함수를 사용하면 전역이 아닌 스레드 자체 저장소 내에서이 포인터를 지정할 수 있습니다. 이 저장소는 호출 함수 전용이므로 중단했다가 다시 입력 (재진입) 할 수 있으며 대부분의 경우 함수가 구현하는 것 이상의 상호 배제가 작동하는 데 필요하지 않기 때문에 종종 다음과 같이 간주됩니다. 스레드로부터 안전 합니다. 그러나 이것은 정의에 의해 보장되지 않습니다.

그러나 errno는 POSIX 시스템에서 약간 다른 경우입니다 (그리고 이것이 어떻게 작동하는지에 대한 설명에서 이상한 경향이 있습니다) :)

간단히 말해서, 재진입은 종종 스레드 안전을 의미하지만 ( "스레드를 사용하는 경우 해당 함수의 재진입 버전 사용"에서와 같이) 스레드 안전이 항상 재진입을 의미하지는 않습니다 (또는 그 반대). 스레드 안전성 을 고려할 때 동시성 은 고려해야 할 사항입니다. 함수를 사용하기 위해 잠금 및 상호 배제 수단을 제공해야하는 경우 함수는 본질적으로 스레드로부터 안전하지 않습니다.

그러나 모든 기능을 검사 할 필요는 없습니다. malloc()재진입 할 ​​필요가 없으며 주어진 스레드의 진입 점 범위를 벗어난 항목에 의존하지 않습니다 (그 자체가 스레드로부터 안전함).

정적으로 할당 된 값을 반환하는 함수 는 뮤텍스, 퓨 텍스 또는 기타 원자 잠금 메커니즘을 사용하지 않으면 스레드로부터 안전하지 않습니다 . 그러나 중단되지 않을 경우 재진입 할 ​​필요가 없습니다.

즉 :

static char *foo(unsigned int flags)
{
  static char ret[2] = { 0 };

  if (flags & FOO_BAR)
    ret[0] = 'c';
  else if (flags & BAR_FOO)
    ret[0] = 'd';
  else
    ret[0] = 'e';

  ret[1] = 'A';

  return ret;
}

따라서 보시다시피 여러 스레드가 잠금을 사용하지 않고 사용하는 것은 재앙이 될 수 있지만 재진입 할 ​​목적은 없습니다. 일부 임베디드 플랫폼에서 동적으로 할당 된 메모리가 금기시되는 상황에 직면하게 될 것입니다.

순전히 함수형 프로그래밍에서 재진입은 종종 스레드 안전을 의미 하지 않으며 함수 진입 점, 재귀 등에 전달 된 정의 된 또는 익명 함수의 동작에 따라 달라집니다.

'스레드 안전'을 설정하는 더 좋은 방법 은 동시 액세스에 안전하며 , 이는 필요성을 더 잘 보여줍니다.


2
재진입은 스레드 안전을 의미하지 않습니다. 순수 함수는 스레드 안전성을 의미합니다.
Julio Guerra

훌륭한 대답 Tim. 명확히하기 위해, "자주"로부터 제가 이해 한 것은 스레드 안전성이 재진입을 의미하지 않고 재진입도 스레드 안전성을 의미하지 않는다는 것입니다. 스레드로부터 안전 하지 않은 재진입 함수의 예를 찾을 수 있습니까?
Riccardo

@ Tim Post "요컨대, 재진입이란"스레드를 사용하는 경우 해당 함수의 재진입 버전 사용 "과 같이 스레드 안전을 의미하는 경우가 많지만 스레드 안전이 항상 재진입을 의미하지는 않습니다." qt 반대를 말합니다 . "따라서 스레드로부터 안전한 함수는 항상 재진입이 가능하지만 재진입 함수가 항상 스레드로부터 안전한 것은 아닙니다."
4pie0 2015

그리고 wikipedia 또 다른 것을 말합니다 . "재진입에 대한이 정의는 다중 스레드 환경의 스레드 안전성과 다릅니다. 재진입 서브 루틴은 스레드 안전성을 달성 할 수 있지만 [1] 재진입만으로는 스레드 안전성이 충분하지 않을 수 있습니다. 반대로 스레드로부터 안전한 코드가 반드시 재진입 할 ​​필요는 없습니다 (...) "
4pie0

@Riccardo : 휘발성 변수를 통해 동기화되지만 신호 / 인터럽트 핸들러와 함께 사용하기위한 전체 메모리 장벽이 아닌 함수는 일반적으로 재진입 가능하지만 스레드로부터 안전합니다.
doynax

79

요약 : 함수는 재진입, 스레드 안전 또는 둘 다일 수 있습니다.

스레드 안전성재진입에 대한 Wikipedia 기사는 읽을 가치가 있습니다. 다음은 몇 가지 인용입니다.

다음과 같은 경우 함수는 스레드로부터 안전 합니다.

동시에 여러 스레드에 의한 안전한 실행을 보장하는 방식으로 만 공유 데이터 구조를 조작합니다.

다음과 같은 경우 함수가 재진입 됩니다.

실행 중 언제든지 중단 될 수 있으며 이전 호출이 실행을 완료하기 전에 안전하게 다시 호출 ( "다시 입력") 할 수 있습니다.

재진입 가능성의 예로서 Wikipedia는 시스템 인터럽트에 의해 호출되도록 설계된 함수의 예를 제공합니다. 다른 인터럽트가 발생할 때 이미 실행 중이라고 가정합니다. 그러나 시스템 인터럽트로 코딩하지 않는다고해서 안전하다고 생각하지 마십시오. 콜백이나 재귀 함수를 사용하면 단일 스레드 프로그램에서 재진입 문제가 발생할 수 있습니다.

혼동을 피하기위한 핵심은 재진입이 실행중인 스레드 하나만 참조한다는 것입니다. 멀티 태스킹 운영 체제가 존재하지 않았던 시대의 개념입니다.

(Wikipedia 기사에서 약간 수정 됨)

예 1 : 스레드로부터 안전하지 않고 재진입이 아님

/* As this function uses a non-const global variable without
   any precaution, it is neither reentrant nor thread-safe. */

int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

예 2 : 스레드로부터 안전하고 재진입이 아님

/* We use a thread local variable: the function is now
   thread-safe but still not reentrant (within the
   same thread). */

__thread int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

예제 3 : 스레드로부터 안전하지 않고 재진입 가능

/* We save the global state in a local variable and we restore
   it at the end of the function.  The function is now reentrant
   but it is not thread safe. */

int t;

void swap(int *x, int *y)
{
    int s;
    s = t;
    t = *x;
    *x = *y;
    *y = t;
    t = s;
}

예제 4 : 스레드로부터 안전한 재진입

/* We use a local variable: the function is now
   thread-safe and reentrant, we have ascended to
   higher plane of existence.  */

void swap(int *x, int *y)
{
    int t;
    t = *x;
    *x = *y;
    *y = t;
}

10
나는 단지 감사를 표하기 위해 언급해서는 안된다는 것을 알고 있지만 이것은 재진입과 스레드 안전 기능의 차이점을 설명하는 최고의 삽화 중 하나입니다. 특히 매우 간결하고 명확한 용어를 사용했으며 4 가지 범주를 구분하는 훌륭한 예제 기능을 선택했습니다. 감사합니다!
ryyker

11
예를 들어 3은 재진입이 아닌 것 같습니다. 신호 처리기가를 중단하고을 t = *x호출 swap()하면을 호출 t하면 예기치 않은 결과가 발생합니다.
rom1v

1
@ SandBag_1996,의가에 대한 호출 생각해 보자 swap(5, 6)a로 중단중인 swap(1, 2). 후 t=*x, s=t_original하고 t=5. 이제, 중단, 후 s=5t=1. 그러나 두 번째가 swap반환 되기 전에 컨텍스트를 복원하여t=s=5 . 이제, 우리는 첫 번째로 돌아가 swapt=5 and s=t_original이후 계속 t=*x. 따라서 기능이 재진입되는 것처럼 보입니다. 모든 호출은 s스택 에 할당 된 자체 복사본을 가져 옵니다.
urnonav

4
@ SandBag_1996 함수가 중단되면 (어느 시점에서든) 다시 호출 될 뿐이고 원래 호출을 계속하기 전에 완료 될 때까지 기다린다는 가정입니다. 다른 일이 발생하면 기본적으로 멀티 스레딩이며이 함수는 스레드로부터 안전 하지 않습니다 . 함수가 ABCD를 수행한다고 가정하면 AB_ABCD_CD, A_ABCD_BCD 또는 A__AB_ABCD_CD__BCD와 같은 것만 허용합니다. 확인할 수 있듯이 예제 3은 이러한 가정 하에서 잘 작동하므로 재진입이 가능합니다. 도움이 되었기를 바랍니다.
MiniQuark

1
@ SandBag_1996, 뮤텍스는 실제로 재진입이 불가능하게 만듭니다. 첫 번째 호출은 뮤텍스를 잠급니다. 두 번째 호출-교착 상태가 발생합니다.
urnonav

56

정의에 따라 다릅니다. 예를 들어 Qt는 다음을 사용 합니다.

  • 공유 데이터에 대한 모든 참조가 직렬화되기 때문에 호출에서 공유 데이터를 사용하는 경우에도 스레드 안전 * 함수를 여러 스레드에서 동시에 호출 할 수 있습니다.

  • 재진입 기능은 여러 스레드에서 동시에 호출 할 수 있지만, 각 호출은 자신의 데이터를 사용하는 경우에만.

따라서 스레드로부터 안전한 함수는 항상 재진입 가능하지만 재진입 함수가 항상 스레드로부터 안전한 것은 아닙니다.

확장에 의해, 각 스레드가 클래스의 다른 인스턴스를 사용하는 한 클래스는 멤버 함수를 여러 스레드에서 안전하게 호출 할 수있는 경우 재진입 가능 하다고합니다 . 클래스는 스레드로부터 안전합니다.모든 스레드가 클래스의 동일한 인스턴스를 사용하더라도 해당 멤버 함수를 여러 스레드에서 안전하게 호출 할 수있는 경우 합니다.

그러나 그들은 또한주의를 기울입니다 :

참고 : 멀티 스레딩 도메인의 용어는 완전히 표준화되지 않았습니다. POSIX는 C API에 대해 약간 다른 재진입 및 스레드 안전 정의를 사용합니다. Qt와 함께 다른 객체 지향 C ++ 클래스 라이브러리를 사용할 때는 정의를 이해해야합니다.


2
재진입에 대한이 정의는 너무 강력합니다.
qweruiop 2014 년

전역 / 정적 변수를 사용하지 않는 경우 함수는 재진입 가능하고 스레드로부터 안전합니다. 스레드-안전 : 많은 스레드가 동시에 함수를 실행할 때 경쟁이 있습니까 ?? 전역 var를 사용하는 경우 잠금을 사용하여 보호하십시오. 그래서 스레드로부터 안전합니다. 재진입 : 함수 실행 중에 신호가 발생하고 신호에서 함수를 다시 호출하면 안전합니까 ??? 이 경우 여러 스레드가 없습니다. 그것은 당신의 할 일이 재진입, 나처럼 만들기 위해 정적 / 글로벌 VAR를 사용하지 않는 것이 좋습니다 예 3
keniee 반
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.