서버/멀티스레드

13. Lock-Free Stack #1

광란의슈가슈가룬 2024. 7. 29. 19:19

전에 락 기반 큐/스택을 만들어 보았는데 이번에는 락을 사용하지 않는 스택에 대해 알아보자.

Lock-Free는 이름만 보면 락을 사용하지 않아 더 빠를 것이라고 생각이 되는데 실상은 그렇지 않다.

락을 사용하지 않기 때문에 동기화 작업을 해주어야 하고 그로 인해서 성능 차이가 거의 나지 않는다고 볼 수 있다.

 

락 프리 스택은 락 기반 스택처럼 기존의 스택을 락으로 감싸는 형식이 아니라 아예 새로 만들어야 한다.

template<typename T>
class LockFreeStack
{
	struct Node
	{
		Node(const T& value) :data(value)
		{
		}

		T data;
		Node* next;
	};
public:

private:
	atomic<Node*> _head;
};

기본적인 스택의 틀이다. 이제 Push와 Pop을 만들어보자.

 

void Push(const T& value)
{
	Node* node = new Node(value);
	node->next = _head;
	_head = node;
}

싱글스레드라면 위와 같이 구현을 하면 문제없이 잘 돌아간다. 하지만 멀티스레드는 다르다. 딱 보면 알 수 있듯이 node->next에 head를 넣는 작업과 head에 node를 넣는 작업 사이에 다른 스레드가 접근하게 되면 한 노드는 버려지게 된다.(접근조차 할 수 없게 되어버린다!) 따라서 CAS 기법을 사용하여 이러한 문제를 해결해야 한다.

 

void Push(const T& value)
{
	Node* node = new Node(value);
	node->next = _head;
    
	while (_head.compare_exchange_strong(node->next, node) == false)
	{
	}
	/*
	compare_exchange_strong 작동방식
	if (_head == node->next)
	{
		_head = node;
        
		return true;
	}
	else
	{
		node->next = _head;

		return false;
	}
	*/
}

compare_exchange_strong을 활용하여 Push함수를 수정한 모습이다. 예전의 코드에서는 while문 안에서 초기값을 다시 세팅해주었는데 공교롭게도 이 코드의 경우 compare_exchange_strong를 만족하지 못할 경우 실행되는 node->next = _head의 과정이 우리가 원하는 과정과 같으므로 따로 초기화를 해주지 않았다.

 

bool TryPop(T& value)
{
	Node* oldHead = _head;

	while (oldHead && (_head.compare_exchange_strong(oldHead, oldHead->next) == false))
	{
	}

	if (oldHead == nullptr)
		return false;

	value = oldHead->data;

	//문제 발생
	delete oldHead;
	return true;
}

지난 글과 같이 TryPop을 간단히 만들어 보았다. oldHead라는 변수를 두어 _head와 CAS을 해주도록 하였다. oldHead가 null일 수 있으므로 while문에 조건을 추가하였다. 문제 없어보이는 이 코드도 delete를 할 때 문제가 생기는데 서로 다른 스레드에서 oldHead가 같은 노드를 가르키고 있을 수가 있다. 이 때 한 스레드가 그 노드를 삭제하게 되면 다른 스레드는 가르킬 수 없는 위치를 가르키게 되므로 크래시가 나버리게 된다. C#이나 Java의 경우는 가비지 컬렉터가 있으므로 따로 delete를 해주지 않아도 자동으로 메모리를 해제해주기 때문에 delete를 해주는 코드를 빼면 해결이 된다. 하지만 C++은 그러한 기능이 없으므로 다른 방법을 사용해야 한다. 아무도 그 노드를 참조하지 않을 때까지 기다렸다가 해제를 해주는 것이 핵심으로 구현 방법은 여러가지이다.

 

락 프리는 현재 많은 연구가 이루어지고 있고 논문 뿐만 아니라 특허로 등록 되어있는 기법도 있다고 한다. 그래서 어떠한 기법을 알더라도 실제로 상용화 할 때 사용할 수 있을지는 미지수라고 한다.(새로운 사실을 알게 되었다...)

'서버 > 멀티스레드' 카테고리의 다른 글

15. ThredManager  (0) 2024.08.01
14. Lock-Free Stack #2  (0) 2024.07.30
12. Lock-Based Queue/Stack  (0) 2024.07.26
11. Thread Local Storage  (3) 2024.07.25
10. 메모리 모델  (0) 2024.07.24