1. 어떻게 안전하게 정의됩니까?
의미 적으로. 이 경우 이것은 명확한 용어가 아닙니다. 그것은 단지 "위험없이 그렇게 할 수 있습니다"를 의미합니다.
2. 프로그램을 동시에 안전하게 실행할 수 있다면 항상 재진입한다는 의미입니까?
아니.
예를 들어, 잠금과 콜백을 매개 변수로 사용하는 C ++ 함수를 만들어 봅시다.
#include <mutex>
typedef void (*callback)();
std::mutex m;
void foo(callback f)
{
m.lock();
// use the resource protected by the mutex
if (f) {
f();
}
// use the resource protected by the mutex
m.unlock();
}
다른 함수는 동일한 뮤텍스를 잠글 필요가 있습니다.
void bar()
{
foo(nullptr);
}
첫눈에 모든 것이 정상인 것 같습니다… 그러나 기다리십시오 :
int main()
{
foo(bar);
return 0;
}
뮤텍스의 잠금이 재귀 적이 지 않으면 메인 스레드에서 다음과 같은 일이 발생합니다.
main
전화 foo
합니다.
foo
자물쇠를 얻을 것입니다.
foo
호출 bar
호출하는 foo
.
- 두 번째
foo
는 잠금을 획득하려고 시도하고 실패하고 잠금이 해제 될 때까지 기다립니다.
- 이중 자물쇠.
- 죄송합니다…
좋아, 나는 콜백을 사용하여 바람을 피웠다. 그러나 비슷한 효과를 갖는 더 복잡한 코드를 상상하기 쉽습니다.
3. 코드에 재진입 기능이 있는지 확인하면서 명심해야 할 6 가지 요점 사이의 공통 스레드는 무엇입니까?
당신은 할 수 있습니다 냄새 함수가 / 수정 가능한 지속적인 리소스에 대한 액세스를 제공, 또는이 / 함수에 액세스 할 경우 문제가 냄새 .
( 좋아요, 우리 코드의 99 %가 냄새를 맡아야합니다. 그러면 마지막 섹션을 참조하십시오… )
따라서 코드를 연구 할 때 그 중 하나가 경고해야합니다.
- 함수에는 상태가 있습니다 (예 : 전역 변수 또는 클래스 멤버 변수에 액세스)
- 이 함수는 여러 스레드에 의해 호출되거나 프로세스가 실행되는 동안 스택에 두 번 나타날 수 있습니다 (즉, 함수가 직접 또는 간접적으로 호출 될 수 있음). 매개 변수로 콜백을 취하는 함수는 냄새 가 많이납니다.
비재 통화는 바이러스 성입니다. 재진입 가능하지 않은 함수를 호출 할 수있는 함수는 재진입 자로 간주 될 수 없습니다.
또한 C ++ 메소드 는에 액세스 할 수 있기 때문에 냄새가납니다this
. 따라서 코드를 연구하여 재미있는 상호 작용이 없는지 확인해야합니다.
4.1. 모든 재귀 함수는 재진입합니까?
아니.
다중 스레드의 경우 공유 리소스에 액세스하는 재귀 함수가 동시에 여러 스레드에 의해 호출되어 데이터가 잘못 / 손상 될 수 있습니다.
단일 스레드의 경우 재귀 함수는 재 진행이 아닌 함수 (예 : infamous strtok
)를 사용하거나 데이터가 이미 사용중인 사실을 처리하지 않고 전역 데이터를 사용할 수 있습니다. 따라서 함수는 직접 또는 간접적으로 호출되기 때문에 재귀 적이지만 여전히 재귀 적 안전하지 않을 수 있습니다.
4.2. 모든 스레드 안전 기능이 재진입합니까?
위의 예에서 분명히 스레드 안전 기능이 재진입되지 않은 방법을 보여주었습니다. 좋아, 나는 콜백 매개 변수 때문에 바람을 피웠다. 그러나 스레드가 비 재귀 잠금을 두 번 획득하여 스레드를 교착 상태로 만드는 방법에는 여러 가지가 있습니다.
4.3. 재귀 및 스레드 안전 기능이 모두 재진입합니까?
"재귀"가 "재귀 안전"을 의미하는 경우 "예"라고 대답합니다.
여러 스레드에서 동시에 함수를 호출 할 수 있고 문제없이 직접 또는 간접적으로 호출 할 수있는 경우 재진입됩니다.
문제가이 보증을 평가하는 중입니다… ^ _ ^
5. 재진입 및 나사산 안전과 같은 용어는 절대적입니까, 즉 고정 된 구체적인 정의가 있습니까?
나는 그렇게 생각하지만 함수를 평가하는 것이 스레드로부터 안전하거나 재진입이 어려울 수 있습니다. 이것이 위에서 냄새 라는 용어를 사용한 이유입니다 . 함수가 재진입되지는 않지만 복잡한 코드가 재진입되는 것을 확인하기는 어려울 수 있습니다
6. 예
리소스를 사용해야하는 한 가지 방법으로 개체가 있다고 가정 해 보겠습니다.
struct MyStruct
{
P * p;
void foo()
{
if (this->p == nullptr)
{
this->p = new P();
}
// lots of code, some using this->p
if (this->p != nullptr)
{
delete this->p;
this->p = nullptr;
}
}
};
첫 번째 문제는 어떻게 든이 함수가 재귀 적으로 호출되는 경우 (즉,이 함수가 직접 또는 간접적 this->p
으로 호출되는 경우) 마지막 호출이 끝날 때 삭제되고 여전히 종료 전에 사용되기 때문에 코드가 중단 될 수 있다는 것입니다 첫 번째 통화의.
따라서이 코드는 재귀 적으로 안전 하지 않습니다 .
이 문제를 해결하기 위해 참조 카운터를 사용할 수 있습니다.
struct MyStruct
{
size_t c;
P * p;
void foo()
{
if (c == 0)
{
this->p = new P();
}
++c;
// lots of code, some using this->p
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
}
};
이 방법은 코드는 재귀 안전이됩니다 ...하지만 여전히 문제 때문에 멀티 스레딩의 재진입되지 않습니다 : 우리가해야 확인의 수정 c
과의 p
사용하여, 원자 수행됩니다 재귀 뮤텍스를 (모든 뮤텍스는 재귀) :
#include <mutex>
struct MyStruct
{
std::recursive_mutex m;
size_t c;
P * p;
void foo()
{
m.lock();
if (c == 0)
{
this->p = new P();
}
++c;
m.unlock();
// lots of code, some using this->p
m.lock();
--c;
if (c == 0)
{
delete this->p;
this->p = nullptr;
}
m.unlock();
}
};
그리고 물론 이것은 모두 lots of code
의 사용을 포함하여 그 자체가 재진입 이라고 가정합니다 p
.
그리고 위의 코드는 원격으로 예외적으로 안전 하지는 않지만 이것은 또 다른 이야기입니다 ... ^ _ ^
7. 우리 코드의 99 %는 재진입이 아닙니다!
스파게티 코드는 매우 사실입니다. 그러나 코드를 올바르게 분할하면 재진입 문제를 피할 수 있습니다.
7.1. 모든 기능이 NO 상태인지 확인하십시오
매개 변수, 자체 로컬 변수, 상태가없는 기타 함수 만 사용해야하며 데이터가 전혀 리턴되지 않으면 데이터 사본을 리턴해야합니다.
7.2. 객체가 "재귀 적 안전"인지 확인하십시오
객체 메소드는에 액세스 할 수 this
있으므로 동일한 객체 인스턴스의 모든 메소드와 상태를 공유합니다.
따라서 전체 객체를 손상시키지 않고 스택의 한 지점 (예 : 호출 방법 A)에서 다른 지점 (예 : 호출 방법 B)에서 객체를 사용할 수 있어야합니다. 메소드를 종료 할 때 오브젝트가 안정적이고 올바른지 확인하도록 오브젝트를 설계하십시오 (매달려있는 포인터, 모순되는 멤버 변수 등 없음).
7.3. 모든 물체가 올바르게 캡슐화되어 있는지 확인하십시오
다른 사람은 내부 데이터에 액세스 할 수 없습니다.
// bad
int & MyObject::getCounter()
{
return this->counter;
}
// good
int MyObject::getCounter()
{
return this->counter;
}
// good, too
void MyObject::getCounter(int & p_counter)
{
p_counter = this->counter;
}
const 참조를 반환하는 코드가 const 참조를 알려주지 않고 코드의 다른 부분이 데이터를 수정할 수 있으므로 사용자가 데이터의 주소를 검색하면 const 참조를 반환하는 것조차 위험 할 수 있습니다.
7.4. 객체가 스레드로부터 안전하지 않다는 것을 사용자에게 알리십시오
따라서 사용자는 뮤텍스를 사용하여 스레드간에 공유되는 객체를 사용해야합니다.
STL의 개체는 성능 문제로 인해 스레드로부터 안전하지 않도록 설계되었으므로 사용자 std::string
가 두 스레드간에 공유 하려면 동시성 기본 형식으로 액세스를 보호해야합니다.
7.5. 스레드 안전 코드가 재귀 적으로 안전해야합니다
이는 동일한 리소스를 동일한 스레드에서 두 번 사용할 수 있다고 생각되면 재귀 뮤텍스를 사용한다는 의미입니다.