C ++ 26'da blok olmayan yığınlar: Tehlike bölümlerinin uygulanmasına ilişkin ayrıntılar

Adanali

Active member
C ++ 26'da blok olmayan yığınlar: Tehlike bölümlerinin uygulanmasına ilişkin ayrıntılar
Son yazımda tehlikelerin tehlikelerinin uygulanmasını sundum: tehlikeli bölümlerin uygulanmasına sahip blokları olmayan bir yığın. Bugün uygulamayı açıklayacağım.MyNode Veri türüne göre parametrelendirilen bir sınıf sınıfıdır: data. MyNode Kavramı modelleyin Node.








Rainer Grimm yıllardır yazılım mimarı, ekip ve eğitim müdürü olarak çalıştı. C ++ programlama dilleri, Python ve Haskell hakkında makaleler yazmayı seviyor, ancak uzman konferanslarla konuşmayı da seviyor. Modern C ++ blogunda, C ++ tutkusuyla yoğun bir şekilde ilgileniyor.







template <typename T>
concept Node = requires(T a) {
{T::data};
{ *a.next } -> std::same_as<T&>;
};

template <typename T>
struct MyNode {
T data;
MyNode* next;
MyNode(T d): data(d), next(nullptr){ }
};



Kavramlar derleme süresi boyunca vaaz edilir. Modellerin parametreleri için anlamsal kısıtlamaları tanımlarlar. Kavram Node gerekmek data-Member ve bir işaretçi nextBir Node geri gelmek.








(Resim: Rainer Grimm)



LockFreestackhazardpintrs.cpp programının veri türleri esasen bu konuda data-Member ve konsept Node parametrelendirilmiş. MyNode Kavramı modelleyin Node. Burada, örneğin, bildirimi LockFreeStack:


template<typename T, Node MyNode = MyNode<T>>

class LockFreeStack;


Blokları olmayan yığını olan program nasıl görünür:


Template<typename T, Node MyNode = MyNode<T>>
class LockFreeStack {

std::atomic<MyNode*> head;
RetireList<T> retireList;

public:
LockFreeStack() = default;
LockFreeStack(const LockFreeStack&) = delete;
LockFreeStack& operator= (const LockFreeStack&) = delete;

void push(T val) {
MyNode* const newMyNode = new MyNode(val);
newMyNode->next = head.load();
while( !head.compare_exchange_strong(newMyNode->next, newMyNode) );
}

T topAndPop() {
std::atomic<MyNode*>& hazardPointer = getHazardPointer<T>();
MyNode* oldHead = head.load();
do {
MyNode* tempMyNode;
do {
tempMyNode = oldHead;
hazardPointer.store(oldHead);
oldHead = head.load();
} while( oldHead != tempMyNode );
} while( oldHead && !head.compare_exchange_strong(oldHead, oldHead->next) ) ;
if ( !oldHead ) throw std::eek:ut_of_range(„The stack is empty!“);
hazardPointer.store(nullptr);
auto res = oldHead->data;
if ( retireList.isInUse(oldHead) ) retireList.addNode(oldHead);
else delete oldHead;
retireList.deleteUnusedNodes();
return res;
}
};



Arama push Rekabet açısından kritik değil, orada head Atomik bir pasajda güncellenir. Ayrıca, çağrı garantisi compare_exchange_strongO head Her zaman yığının mevcut başı.

Tehlikelerin tehlikeleri nedeniyle çağrı topAndPop daha karmaşık. Her şeyden önce, işlev ifade eder getHazardPointer Geçerli iş parçacığı için tehlike işaretçisi üzerinde. Arama hazardPointer.store(oldHead) Mevcut konuyu tehlike işaretçisinin sahibi ve çağrı yapar hazardPointer.store(nullptr) mülkiyetini serbest bırakır.

Önce iç ve dış döngüleri analiz edin. Dahili döngü, tehlike işaretçisini yığının başına koyar. Aşağıdakileri uygularken do-döngü biter: oldHead == tempNode.

Her iki düğüm de aynı oldHead Hala bloklar olmadan yığının mevcut başıdır. oldHead Ayrılmıştı ve farklı bir iş parçacığı gelebileceğinden ve artık mevcut kafa olamazdı ve oldHead zaten yönetildi.

Harici do-while döngüsü, blok olmayan önceki yığın uygulamalarıyla aynıdır. Itarere benimle birlikte döngü compare_exchange_strong Ve başını koy oldHead->next. Sonunda Head Yığının başı. . Memberfunktion topAndPop Kafanın değerini döndürmeli ve çıkarmalıdır. Benden Önce oldHead Kullan, kontrol etmeliyim oldHead Sıfır bir işaretçi değil. Bu durumda bir istisna yapıyorum.

Geri kalanı topAndPop Basit. Arama retireList.isInUse(oldHead) Kontrol edilip oldHead Hala kullanılır. Bu incelemenin sonucuna bağlı olarak oldHead seçim listesinin retireList.addNode Henüz listede değilse veya ortadan kaldırılmışsa eklendi. Son çağrı retireList.deleteUnusedNodes Üye işlevindeki en yoğun çağrı topAndPop. Üye işlevi retireListe.deleteUnusedNodes Bütün içinden geçer Retire-Beri artık kullanılmayan tüm düğümleri listeleyin ve silin.

Performans nedenleriyle, çağrı retireList.deleteUnusedNodes Her aradığın zaman değil topAndPop Bu daha iyi bir strateji, üyenin işlevi deleteUnusedNodes uzunluğunun ne zaman Retire-List belirli bir eşiği aşar. Eğer uzunluğu Retire-Liste, örneğin, yığının en uzun iki katıdır, düğümlerin en az yarısı ortadan kaldırılabilir. Bu eşik, performans ve depolama tüketimi gereksinimleri arasında bir uzlaşmadır.

İşte işlev getHazardPointer:


template <typename T, Node MyNode = MyNode<T>>
sttd::atomic<MyNode*>& getHazardPointer() {
thread_local static HazardPointerOwner<T> hazard;
return hazard.getPointer();
}


İşlev, sahibini kullanan bir tehlike işaretçisini ifade eder hazardBir iplik restoranı ve statik bir değişken. Bu nedenle, her iş parçacığı, Tehlike İşaretçisi'nin sahibinin kopyasını alır ve süresi sahip olunan iş parçacığının süresine bağlıdır. Tehlikeli işaretçinin sahibine bağlanan süre çok önemlidir, çünkü tehlikeli işaretçi ipliğinin iş parçacığının sahipleri yok edilirse tehlikeli işaretçi tarafından ortadan kaldırıldığı garanti edilir. Veri türü analizimde bu RAII nesnesi hakkında daha fazla yazıyorum HazardPointerOwner.

Sırada ne var?


Bir sonraki makalemde kalan uygulamayı açıklayacağım.


(RME)
 
Üst