리소스를 로딩한다거나 어떤 연산을 수행하기 위해 thread를 만들어서 동작시킬 수 있지만 thread가 언제 끝날지 보장할 수 없기 때문에 이것에 대해 방어적인 코딩이 필요하다.
예를 들어 리소스를 thread로 로딩하고 로딩이 완료될 때 마다 해당 인스턴스로 callback을 하여 리소스 로딩이 완료되었음을 알려주는 구현이라고 가정하자.
이 인스턴스는 자신이 요청한 thread가 완료될 때 까지 삭제하면 안되지만 thread가 언제 끝날지 보장할 수 없기 때문에 thread가 언제 끝나더라도 동작하도록 구현할 필요가 있다.
unsafe callback with raw pointer
기존 pointer를 사용해서 구현할 경우 아래와 비슷한 흐름을 가지게 될 것이다.
class Character {
public:
void PrintItems() {
for each (int item in _items) {
std::cout << "item : " << item << std::endl;
}
}
void PushItem(int item) {
_items.push_back(item);
}
private:
std::vector<int> _items;
};
void LoadingThread(std::function<void()> callback) {
Sleep(1000);
callback();
}
void main() {
Character* pChar = new Character();
std::thread* loading;
pChar->PushItem(1);
pChar->PushItem(5);
pChar->PushItem(10);
auto callback = std::bind(&Character::PrintItems, pChar);
callback();
loading = new std::thread(LoadingThread, printItems);
delete pChar;
std::cout << "deleted pChar" << std::endl;
loading->join();
if (loading) delete loading;
}
위 경우 main에서 호출하는 callback()은 정상 동작한다.
하지만 LoadingThread가 1초 대기 후에 callback을 호출하는 반면 thread를 만들자 마자 바로 pChar를 메모리 해제하기 때문에 1초 후에 thread가 callback을 호출하면 접근 에러가 발생한다.
실제로 로딩중인 캐릭터가 맞아서 죽거나 view에서 벗어나서 지워야 할 경우 위와 같은 상황이 발생할 수 있다.
이는 raw pointer는 가리키고 있는 메모리가 해제되더라도 그것을 인지하지 못한 채 계속 해당 메모리를 가리키기 때문인데, 누군가 해당 메모리를 가리키고 있다면 그 메모리를 해제하지 못하도록 할 필요가 있다.
thread safe callback with smart pointer
void SafePrint(std::weak_ptr<Character> w) {
auto pChar = w.lock();
if (pChar) {
pChar->PrintItems();
}
}
void main() {
auto pChar = std::make_shared<Character>();
std::thread* loading;
pChar->PushItem(1);
pChar->PushItem(5);
pChar->PushItem(10);
auto callback = std::bind(&SafePrint, std::weak_ptr<Character>(pChar));
loading = new std::thread(LoadingThread, callback);
pChar.reset(); // delete pChar
std::cout << "deleted pChar" << std::endl;
loading->join();
if (loading) delete loading;
}
weak_ptr을 lock()으로 shared_ptr로 변환시킬 수 있는데, 이 때 shared_ptr이 가리키는 객체의 ref count가 main함수의 reset()으로 인해 0이 되었기 때문에 이미 메모리에서 해제된 상태이다.
shared_ptr은 메모리에서 해제시키면서 해당 영역을 NULL로 만들어주기 때문에 단순한 조건문으로 실제로 인스턴스가 메모리에 살아있는지 검사할 수 있다. 아직 메모리에 있을 경우 callback을 수행하며 이미 지워진 경우 callback 자체를 무시하도록 해서 안전하게 구현할 수 있다.
출처 : c++11 - std::shared_ptr로 thread safe callback 구현하기 (tistory.com)
'프로그래밍 언어 > C++' 카테고리의 다른 글
[C++] to_string 함수에 대해서, 특정 타입 > 문자열 (0) | 2023.07.20 |
---|---|
C++ 전처리기로 코드 영역 블록 설정 (0) | 2023.03.25 |
C 조건부 전처리기 (0) | 2023.01.03 |
C++ 클래스 템플릿에 선언된 friend 함수를 외부에 정의하는 방법 (0) | 2023.01.03 |
C/C++ 예외상황에서의 포인터의 동작 (0) | 2022.12.17 |