[c] 스레드 세이프 vs 재진입

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

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

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



답변

재진입 함수는 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;
}

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

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

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


답변

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

스레드 안전성재진입에 대한 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;
}


답변

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

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

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

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

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

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

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


답변