programing

스레드 세이프와 재진입

yoursource 2022. 7. 16. 00:54
반응형

스레드 세이프와 재진입

최근에 '말록 스레드는 안전한가?'라는 제목으로 '말록 재진입은 안전한가?'라는 질문을 했습니다.

나는 모든 재수생이 안전하다고 생각했다.

이 추정이 잘못된 건가요?

TL;DR: 함수는 재진입, 스레드 세이프, 둘 다 또는 둘 다 할 수 있습니다.

스레드 안전과 재진입에 관한 위키피디아 기사들은 읽을 가치가 있다.인용문은 다음과 같습니다.

함수는 다음과 같은 경우 스레드 세이프입니다.

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

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

실행 중 언제든지 중단되고 이전 호출 실행이 완료되기 전에 안전하게 다시 호출("재호출")할 수 있습니다.

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

혼란을 피하기 위한 열쇠는 재진입이 실행되는 쓰레드는 1개뿐이라는 것입니다.멀티태스킹 운영체제가 존재하지 않았던 시절의 개념입니다.

(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++ 클래스 라이브러리를 사용할 경우 정의를 이해해야 합니다.

재입력 함수는 C 라이브러리 헤더에 표시되는 글로벌 변수에 의존하지 않습니다.예를 들어 C에서 strtok() vs strtok_r()를 취합니다.

일부 함수는 '진행 중인 작업'을 저장할 장소가 필요합니다. 재진행 함수를 사용하면 이 포인터를 전역이 아닌 스레드 자체 스토리지 내에서 지정할 수 있습니다.이 스토리지는 호출 함수에 독점되어 있기 때문에 중단 및 재진입(재진입)이 가능하며, 대부분의 경우 함수가 구현한 것 이상의 상호 배제는 필요하지 않기 때문에 스레드 세이프인 으로 간주됩니다.그러나 이것은 정의상 보장되지 않습니다.

단, errno는 POSIX 시스템에서는 약간 다른 경우입니다(또한 이 모든 것이 어떻게 기능하는지에 대한 설명에서는 홀수인 경우가 많습니다).

간단히 말해, 재진입이란 종종 스레드 세이프를 의미하지만('스레드를 사용하는 경우 해당 기능의 재진입 버전을 사용'에서와 같이), 스레드 세이프가 항상 재진입(또는 그 반대)을 의미하는 것은 아닙니다.스레드 세이프티를 검토할 때는 동시성을 고려해야 합니다.함수를 사용하기 위해 잠금 및 상호 제외 수단을 제공해야 하는 경우 함수는 본질적으로 스레드 세이프가 아닙니다.

그러나 모든 기능을 검사할 필요는 없습니다. malloc()는 재진입할 필요가 없습니다.특정 스레드의 엔트리 포인트 범위 외의 것에 의존하지 않습니다(및 그 자체가 스레드 세이프입니다).

정적으로 할당된 값을 반환하는 함수는 뮤텍스, futex 또는 기타 원자 잠금 메커니즘을 사용하지 않으면 스레드 세이프가 되지 않습니다.하지만 방해받지 않는다면 재진입할 필요가 없습니다.

예:

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;
}

따라서 여러 스레드가 잠금 기능을 사용하지 않고 여러 스레드를 사용하는 것은 재앙이지만 재진입의 목적은 없습니다.일부 임베디드 플랫폼에서는 동적으로 할당된 메모리가 금지되어 있을 때 이와 같은 문제가 발생합니다.

순수하게 기능하는 프로그래밍에서 재진입자는 종종 스레드 안전성을 의미하지 않습니다. 이는 함수 진입점에 전달되는 정의되거나 익명 함수의 동작, 재귀 등에 의존합니다.

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

언급URL : https://stackoverflow.com/questions/856823/threadsafe-vs-re-entrant

반응형