스레드 세이프와 재진입
최근에 '말록 스레드는 안전한가?'라는 제목으로 '말록 재진입은 안전한가?'라는 질문을 했습니다.
나는 모든 재수생이 안전하다고 생각했다.
이 추정이 잘못된 건가요?
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
'programing' 카테고리의 다른 글
IntelliJ는 사용방법에 대한 메서드 파라미터 힌트를 보여줍니다.사용하지 않는 방법 (0) | 2022.07.17 |
---|---|
vue / composition api를 사용하여 parent에서 하위 구성 요소 메서드를 호출합니다. (0) | 2022.07.17 |
초기화되지 않은 포인터에 데이터를 복사/스캔/읽을 때 크래시 또는 "세그멘테이션 장애"가 발생합니다. (0) | 2022.07.16 |
봄에 프로그래밍 방식으로 현재 활성/기본 환경 프로파일을 얻으려면 어떻게 해야 합니까? (0) | 2022.07.16 |
Java에서 다른 클래스의 개인 필드 값을 읽는 방법은 무엇입니까? (0) | 2022.07.16 |