본문 바로가기

포폴

락프리큐에 TlsPool 붙이기 - 문제 해결

	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이엇으므로 아래의 상태에서 

16진수로 나타냇을때

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

아까엿으면 DOUBLE CAS가 성공해버렷겟지만 이번에는 continue에 걸려서 실패하는 모습이다.