[C++/CLI] Thread Local Storage 사용 문제

  • Post author:
  • Post category:
  • Post last modified:July 13, 2007

Native C++로 작성한 소프트웨어를 C++/CLI로 포팅해야 하는 상황이었다. 컴파일러에 /clr 옵션을 주고 빌드하자 한번에 성공했다. 기뻐하며 F5 키를 눌러 실행시키는 순간, 런타임 오류가 발생했다.

각설하고 이틀 가량 조사하고 테스트한 결과, Thread Local Storage (TLS)의 문제였다. VC++에서는 __declspec 키워드를 사용하여 thread 변수를 선언할 수 있다.

__declspec( thread ) int tls_i = 1;

하지만 /clr 옵션이 들어가는 순간 __declspec 키워드를 쓸 생각 말아야 한다. Jeremy KuhneCLR이 __declspec(thread)을 지원하지 않는다고 밝혔다. 그래서 그는 별도의 자원 관리 클래스를 만들어서, WIN32 API를 호출하게 했다. 컴파일러가 대신 해줄 일을 사람이 직접해야 하는 상황이 연출된 것이다. 물론 클래스만 제대로 설계하면 기존 코드를 손 댈 일이 거의 없긴 하다. Jeremy Kuhne가 소개한 클래스는 이렇게 생겨먹었다.

template <typename T>
class ThreadLocal
{
private:
	DWORD threadLocalIndex;
	ThreadLocal(ThreadLocal const&);

	T *GetPointer(void)
	{
		return static_cast<T*>(::TlsGetValue(this->threadLocalIndex));
	}

	void SetPointer(T *value)
	{
		::TlsSetValue(this->threadLocalIndex, static_cast<void*>(value));
	}

public:
	void SetValue(const T &value)
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer == NULL)
		{
			this->SetPointer(new T(value));
		}
		else
		{
			*currentPointer = value;
		}
	}

	T &GetValue(void)
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer == NULL)
		{
			this->SetPointer(new T());
		}

		return *this->GetPointer();
	}

	void DeleteValue()
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer != NULL)
		{
			delete currentPointer;
			this->SetPointer(NULL);
		}
	}

	ThreadLocal(const T& value)
	{
		this->threadLocalIndex = ::TlsAlloc();
		this->SetValue(value);
	}

	ThreadLocal()
	{
		this-&gt;threadLocalIndex = ::TlsAlloc();
	}

	virtual ~ThreadLocal()
	{
		this->DeleteValue();
		::TlsFree(this->threadLocalIndex);
	}
};

__declspec(thread)가 지원 안 되는 정확한 이유는 모르겠다. 단지 테스트를 통해 이 사실을 확인하고, 그럴듯한 이유를 추정했을 뿐이다(확실하지 않은 내용이라 밝히진 않겠다). Jeremy Kuhne가 쓴 글 외에는 이 사실을 언급한 문서 자체가 없는 듯 하다. 혹시나 싶어 Visual Studio 2008 ‘Orcas’의 베타판으로 컴파일해봤지만, 결과는 마찬가지였다.

버그가 있었다. 초기화와 관련된 버그였는데, 간단하게 고쳐봤다. // modified 부분을 살펴보면 된다. 테스트하다 살짝 고친 코드라 또다른 버그가 있을 수도 있다.

#pragma unmanaged
template <typename T>
class ThreadLocal
{
private:
	DWORD threadLocalIndex;
	ThreadLocal(ThreadLocal const&);

	T *GetPointer(void)
	{
		return static_cast<T*>(::TlsGetValue(this->threadLocalIndex));
	}

	void SetPointer(T *value)
	{
		::TlsSetValue(this->threadLocalIndex, static_cast<void*>(value));
	}

	T t;
public:
	void SetValue(const T &value)
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer == NULL)
		{
			this->SetPointer(new T(value));
		}
		else
		{
			*currentPointer = value;
		}
	}

	T &GetValue(void)
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer == NULL)
		{
			// modified
			this->SetPointer(new T(t));
		}

		return *this->GetPointer();
	}

	void DeleteValue()
	{
		T* currentPointer = this->GetPointer();

		if (currentPointer != NULL)
		{
			delete currentPointer;
			this->SetPointer(NULL);
		}
	}

	ThreadLocal(const T& value)
	{
		this->threadLocalIndex = ::TlsAlloc();
		// modified
		this->t = value;
	}

	ThreadLocal()
	{
		this->threadLocalIndex = ::TlsAlloc();
	}

	virtual ~ThreadLocal()
	{
		this->DeleteValue();
		::TlsFree(this->threadLocalIndex);
	}
};
Author Details
Kubernetes, DevSecOps, AWS, 클라우드 보안, 클라우드 비용관리, SaaS 의 활용과 내재화 등 소프트웨어 개발 전반에 도움이 필요하다면 도움을 요청하세요. 지인이라면 가볍게 도와드리겠습니다. 전문적인 도움이 필요하다면 저의 현업에 방해가 되지 않는 선에서 협의가능합니다.