void Enqueue(T data)
{
using namespace CAddressTranslator;
// 이번에 사용할 상위 17비트값 만들기
uint64_t meta = GetCnt(&metaCnt_);
// 새로운 노드 : next의 상위 17비트값의 초기화와 node의 T타입 생성자도 호출함
Node* pNewNode = nodePool_.Alloc(std::move(data), identifier_, meta);
QNodePtr newTail{ identifier_,GetMetaAddr(meta,(uintptr_t)pNewNode)};
while (true)
{
uintptr_t metaTail = metaTail_;
Node* pRealTail = (Node*)GetRealAddr(metaTail);
QNodePtr nextOfTail = pRealTail->next_;
if (GetRealAddr(nextOfTail.metaAddr_) != (uintptr_t)nullptr)
{
InterlockedCompareExchange(&metaTail_, nextOfTail.metaAddr_, metaTail);
continue;
}
if (ExtractMetaCnt(metaTail) != ExtractMetaCnt(nextOfTail.metaAddr_))
continue;
if (identifier_ != nextOfTail.identifier_) -- 추가됨
continue;
// 1번 CAS
// 바로 위의 if문으로 인해서 pRealTail이 재활용 되었다면 metaNext는 이미 재활용 되기 이전값임을 보장한다 따라서 재활용된경우 무조건 실패한다.
if (InterlockedCompareExchange128((LONG64*)&pRealTail->next_, newTail.metaAddr_, newTail.identifier_, (LONG64*)&nextOfTail) == FALSE)
continue;
if (nextOfTail.identifier_ != identifier_)
__debugbreak();
// 2번 CAS 77번째 라인의 if문안의 CAS 떄문에 실패할수 잇으며 이를통해 스핀락이 아니게됨
InterlockedCompareExchange(&metaTail_, newTail.metaAddr_, metaTail);
InterlockedIncrement(&num_);
return;
}
}
큐 식별자가 0번인 9368 스레드는 pRealTail == 0x000001f7acd2eab0까지 초기화 한 뒤 일시정지 시킴.
이때의 metaTail == 2163268053680로 상위 47비트는 0임.

큐 식별자가 0번인 5428 스레드는 9368스레드의 pRealTail이 바라보는 노드를 디큐에서 풀에 반환한다.

큐 식별자가 1인 15968 스레드는 pRealTail == 0x000001f7acd2eab0 이 가리키는 노드의 큐 식별자와 metaAddr을 초기화햇다. 9368의 metaTail의 상위 17비트는 0이엇으므로 아래의 상태에서



위와 같이 조사식을 수정해서 문제를 재현하고 9368을 제외한 두 스레드를 정지시킨다.

아까엿으면 DOUBLE CAS가 성공해버렷겟지만 이번에는 continue에 걸려서 실패하는 모습이다.
'포폴' 카테고리의 다른 글
| 락프리큐 초기 로깅 - LogAnalyzer 및 디버깅로그 설명 (1) | 2024.11.09 |
|---|---|
| 락프리큐 초기 로깅 - 코드 및 클래스 설명 (0) | 2024.11.09 |
| 락프리큐에 TlsPool 붙이기 - 코드설명 밑 문제제기 (0) | 2024.10.18 |