template<typename T>
class LockFreeStack
{
struct Node
{
Node(const T& value) : data(value), next(nullptr)
{
}
T data;
Node* next;
};
public:
// 1) 새 노드를 만들고
// 2) 새 노드의 next = head
// 3) head = 새 노드
void Push(const T& value)
{
Node* node = new Node(value);
node->next = _head;
while (_head.compare_exchange_weak(node->next, node) == false)
{
}
}
// 1) head 읽기
// 2) head->next 읽기
// 3) head = head->next
// 4) data 추출해서 반환
// 5) 추출한 노드를 삭제
bool TryPop(T& value)
{
++_popCount;
Node* oldHead = _head;
// compare_exchange_weak : _head = oldHead라면 _head에 oldHead->next를 넣어라
while (oldHead && _head.compare_exchange_weak(oldHead, oldHead->next) == false)
{
}
if (oldHead == nullptr)
{
--_popCount;
return false;
}
value = oldHead->data;
// 삭제 대기목록에 넣는다
TryDelete(oldHead);
return true;
}
// 1) 데이터 분리
// 2) Count 체크
// 3) 나 혼자면 삭제
void TryDelete(Node* oldHead)
{
// 나 외에 누가 있는가?
if (_popCount == 1)
{
// 나 혼자네?
// 혼자인걸 확인하는 이유는 내꺼를 함부로 delete해주면 내 데이터를 next로 가지고 있는 애가 죽을 수 있음.
// 이왕 혼자인거, 삭제 예약된 다른 데이터들도 삭제해보자
// exchange : 기존 데이터는 nullptr 넣고 데이터를 빼와 node에 저장
Node* node = _pendingList.exchange(nullptr);
// 여기서 이렇게 다시 한 번 TryPop으로 들어온 부분이 없나 체크하는이유는
// 혹시나 _pendingList의 데이터를 TryPop다른 부분에서 사용할 것을 방지하기 위해서이다.
// 본 코드에서는 런타임 에러는 발생하지 않음.
if (--_popCount == 0)
{
// 끼어든 애가 없음 -> 삭제 진행
// 이제와서 끼어들어도, 어차피 데이터는 분리해둔 상태~!
DeleteNodes(node);
}
else if (node)
{
// 누가 끼어들었으니 다시 갖다 놓자
ChainPendingNodeList(node);
}
// 내 데이터는 삭제
delete oldHead;
}
else
{
// 누가 있네? 그럼 지금 삭제하지 않고, 삭제 예약만
ChainPendingNode(oldHead);
--_popCount;
}
}
void ChainPendingNodeList(Node* first, Node* last)
{
// 두 개의 노드 리스트를 붙이고 _pendingList는 제일 앞 값을 가리킨다
last->next = _pendingList;
while (_pendingList.compare_exchange_weak(last->next, first) == false)
{
}
}
void ChainPendingNodeList(Node* node)
{
Node* last = node;
while (last->next)
last = last->next;
ChainPendingNodeList(node, last);
}
void ChainPendingNode(Node* node)
{
ChainPendingNodeList(node, node);
}
static void DeleteNodes(Node* node)
{
while (node)
{
Node* next = node->next;
delete node;
node = next;
}
}
private:
atomic<Node*> _head;
atomic<uint32> _popCount = 0; // Pop을 실행중인 쓰레드 개수
atomic<Node*> _pendingList; // 삭제 되어야 할 노드들 (첫번째 노드)
};