Ich muss eine riesige Hash-Tabelle implementieren, die mehrere Threads unterstützt, die gleichzeitig eingefügt und abgerufen werden. Die Tasten sind int und die zweiten Elemente sind Vektoren Objekt T.TBB gleichzeitige ungeordnete Karte verursacht segfault
class T {
//class definitions here
}
Derzeit ist die Umsetzung mit TBB :: concurrent_unordered_map geholfen wird. In der Dokumentation scheint es, Einfügen und Durchlaufen gleichzeitig zu ermöglichen. Das Ausführen mit mehreren PThreads führt jedoch zu einem Segmentierungsfehler, obwohl der sequenzielle Test vollkommen in Ordnung ist. Beachten Sie, dass es definitiv keine Lösch- oder Popup-Operationen gibt, d. H. Die Hash-Tabelle darf nur wachsen.
std::vector<T*> get(int key) {
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, std::vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
return it->second;
else {
std::vector<T*> newvector;
return newvector;
}
}
void insert(int key, T *t) {
tbb::concurrent_unordered_map<int, std::vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
std::vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, makepair(key, newTs));
}
}
zu debuggen, was passiert ist, habe ich geändert erste std :: vector :: concurrent_vector, auf TBB und dann die Funktion get ändern() und insert() wie folgt.
std::vector<T*> get_test(int key) {
std::vector<T*> test;
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
test.insert(test.end(), it->second.begin(), it->second.end());
for (T* _t : test)
if (!_t)
printf("Bug happens here!\n"); //Segfault is originated here because a NULL is returned in the vector
return test;
}
void insert_test(int key, T *t) {
//Here t is guaranteed to be not NULL
if(!t)
std::terminate();
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
std::vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, makepair(key, newTs));
}
}
Jetzt können wir den Grund sehen, dass das Parallelprogramm abstürzt wird einige NULL-Zeiger wird in get_test() Funktion zurückgegeben. Erinnern Sie sich, dass in der Funktion insert_test() niemals NULL als zweite Elemente eingefügt wird.
Die folgenden Fragen sind zu stellen.
(1) Ich habe von irgendwo gelesen, dass die gleichzeitige Einfügung in tbb :: concurrent_unordered_map dazu führen kann, dass ein Teil des Einfügeversuchs fehlschlägt und dann die temporären Objekte zerstört werden. Ist das der Grund dafür, dass NULL in der Funktion get_test() beobachtet wird?
(2) Kann TBB das Einfügen und Traversieren wirklich gleichzeitig erlauben? Das bedeutet, während einige Threads eingefügt werden, können die anderen gleichzeitig get() aufrufen.
(3) Gibt es eine bessere Implementierung, d. H. Eine bessere gleichzeitige Hash-Tabelle, die gleichzeitiges Einfügen und Lesen unterstützt?
Wie @for_stack vorgeschlagen habe ich die zweiten Elemente überprüft sind concurrent_vectors und das gesamte Programm ist lauffähig. Ein weiterer Test wird wie folgt durchgeführt:
tbb::concurrent_vector<T*> get_test(int key) {
tbb::concurrent_vector<T*> test;
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
test = it->second;
int i = 0;
for (T* _t : test)
if (!_t)
i++;
if (i > 0)
printf("%d of %lu Ts are NULL\n", i, test.size()); //Segfault is originated here because a NULL is returned in the vector
return test;
}
void insert_test(int key, T *t) {
//Here t is guaranteed to be not NULL
if(!t)
std::terminate();
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
tbb::concurrent_vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, make_pair(key, newTs));
}
}
Der Ausgang ist
1 of 2 Ts are NULL
Das bedeutet, nicht alle Objekte (T) zurück in get() sind NULL.
Wieder ist der sequentielle (oder sogar 1 Thread) läuft in Ordnung.
Schätzen Sie Ihre Erklärung. Ich dachte, dass TBB automatisch eine Sperre für das gleichzeitige Einfügen in den gleichen Schlüssel anwenden wird. Der obige Code sieht jedoch eher wie ein Pseudocode aus und in der tatsächlichen Implementierung ist das zweite Element tatsächlich tbb :: concurrent_vector, und das gesamte Programm stürzt nach längerer Zeit ab (als mit std :: vector). Der Grund ist oben angegeben, da einige NULL-Zeiger in get() zurückgegeben werden, und ich denke nicht, dass das Ändern des Rückgabetyps von get() in concurrent_vector sogar helfen wird. Vielleicht sollte ich den Code bearbeiten, um keine Verwirrung zu verursachen. – defg