std::shared_ptr은 reference counter resource로 구성되어 있습니다.

reference counter 자체는 스레드로부터 '안전' 합니다.

그러나 resource의 접근은 안전하지 않습니다. 

즉, reference counter 수정 원자적(atomic)작업이며, resource가 정확히 한 번 삭제된다는 보장이 있습니다.

 


 

 

 

#include "shared_ptr.h"
#include "shared_ptr.hpp"

using namespace std;

shared_ptr<int> global_ptr;
void thread1()
{
	while (true)
	{
		shared_ptr<int> temp;
		temp = global_ptr;
		if (1 != (*temp)) cout << "Error \n";
	}
}

int main(void)
{
	global_ptr = shared_ptr<int>(new int(1));
	thread th(thread1);

	while (true)
	{
		shared_ptr<int> _newP = shared_ptr<int>(new int(1));
		global_ptr = _newP;
	}

	system("pause");
	return 0;
}

 

 

 

왜 이런 현상이 발생하는지 알아보면,

thread1 에서 

shared_ptr<int> temp; 
temp = global_ptr;


바로 이구간, temp에 글로벌 shared_ptr을 넣는 부분에서 컨텍스트 스위칭이 일어나며 mainThread로 옮겨져 

eb_shared_ptr<int> _newP = eb_shared_ptr<int>(new int(1)); 
global_ptr = _newP;



이 코드가 실행되었다면, thread1에 가지고있는 temp는 값이 사라지게 됩니다. 
따라서 Error텍스트가 노출되고, 스코프를 벗어나며 해제된 메모리에 접근하여 _refCount를 가져와서 크래시까지 발생하게 되는 것입니다. 

 


이 방법을 해결하기 위해서는 두가지 방법이 있습니다. 


1. atomic_load를 사용한다. 

atomic_load를 사용하면 깔끔한 구현이 만들어집니다. 

shared_ptr의 생성과 소멸이 잦은 경우가 아니라면 성능에 큰 지장이 있지도 않을 것 같습니다. 
atomic_load를 살펴보면 내부에 전역 spin lock이 구현되어 있습니다. 
atomic_load을 사용하는 객체는 타입이 다른 shraed_ptr인 경우에도 모두 단 한개의 lock만을 이용하게 될 것입니다. 생성과 소멸이 매우 잦은 경우라면 문제가 생길수도 있다고 생각합니다. 


단점이 있다면 atomic_load를 강제할 수 있도록 만들어 놓지 않는다면 빼먹고 그냥 make_shared를 사용하여 로드를 할 수도 있겠죠.. 

 

 

 

2. c++20에 있는 atomic_shared_ptr을 사용한다. 

 

c++20에서는 atomic_shared_ptr을 사용할 수 있습니다.
이 atomic_shared_ptr은 LockFree로 구현되어 있어 성능상 매우 좋을것으로 생각됩니다. 

 

ref : https://openmynotepad.tistory.com/90

ref : https://agh2o.tistory.com/45

반응형

+ Recent posts