15 Stimmen

Wettlaufbedingung in pthread_once()?

Ich habe eine std::future in einem Thread, der auf eine std::promise die in einem anderen Thread festgelegt wurde.

EDIT: Die Frage wurde mit einer Beispielanwendung aktualisiert, die für immer blockiert wird:

UPDATE: Wenn ich eine pthread_barrier stattdessen macht der folgende Code no Block.

Ich habe eine Testanwendung erstellt, die dies veranschaulicht:

Ganz grundsätzlich Klasse foo erstellt eine thread die eine promise in seiner Ausführungsfunktion und wartet im Konstruktor auf diese promise eingestellt werden. Sobald sie gesetzt ist, wird ein atomic zählen

Dann erstelle ich eine Reihe von diesen foo Objekte, reißen sie ab und überprüfen dann meine count .

#include <iostream>
#include <thread>
#include <atomic>
#include <future>
#include <list>
#include <unistd.h>

struct foo
{
    foo(std::atomic<int>& count)
        : _stop(false)
    {
        std::promise<void> p;
        std::future <void> f = p.get_future();

        _thread = std::move(std::thread(std::bind(&foo::run, this, std::ref(p))));

        // block caller until my thread has started 
        f.wait();

        ++count; // my thread has started, increment the count
    }
    void run(std::promise<void>& p)
    {
        p.set_value(); // thread has started, wake up the future

        while (!_stop)
            sleep(1);
    }
    std::thread _thread;
    bool _stop;
};

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "usage: " << argv[0] << " num_threads" << std::endl;
        return 1;
    }
    int num_threads = atoi(argv[1]);
    std::list<foo*> threads;
    std::atomic<int> count(0); // count will be inc'd once per thread

    std::cout << "creating threads" << std::endl;
    for (int i = 0; i < num_threads; ++i)
        threads.push_back(new foo(count));

    std::cout << "stopping threads" << std::endl;
    for (auto f : threads)
        f->_stop = true;

    std::cout << "joining threads" << std::endl;
    for (auto f : threads)
    {
        if (f->_thread.joinable())
            f->_thread.join();
    }

    std::cout << "count=" << count << (num_threads == count ? " pass" : " fail!") << std::endl;
    return (num_threads == count);
}

Wenn ich dies in einer Schleife mit 1000 Threads ausführe, muss es nur ein paar Mal ausgeführt werden, bis ein Wettlauf stattfindet und einer der futures wird nie aufgeweckt, und deshalb bleibt die App für immer stecken.

# this loop never completes
$ for i in {1..1000}; do ./a.out 1000; done

Wenn ich jetzt SIGABRT die Anwendung, zeigt der resultierende Stack-Trace, dass sie bei der future::wait Der Stack-Trace ist unten zu sehen:

// main thread
    pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    __gthread_cond_wait (__mutex=<optimized out>, __cond=<optimized out>) at libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:846
    std::condition_variable::wait (this=<optimized out>, __lock=...) at ../../../../libstdc++-v3/src/condition_variable.cc:56
    std::condition_variable::wait<std::__future_base::_State_base::wait()::{lambda()#1}>(std::unique_lock<std::mutex>&, std::__future_base::_State_base::wait()::{lambda()#1}) (this=0x93a050, __lock=..., __p=...) at include/c++/4.7.0/condition_variable:93
    std::__future_base::_State_base::wait (this=0x93a018) at include/c++/4.7.0/future:331
    std::__basic_future<void>::wait (this=0x7fff32587870) at include/c++/4.7.0/future:576
    foo::foo (this=0x938320, count=...) at main.cpp:18
    main (argc=2, argv=0x7fff32587aa8) at main.cpp:52

// foo thread
    pthread_once () from /lib64/libpthread.so.0
    __gthread_once (__once=0x93a084, __func=0x4378a0 <__once_proxy@plt>) at gthr-default.h:718
    std::call_once<void (std::__future_base::_State_base::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>&, bool&), std::__future_base::_State_base* const, std::reference_wrapper<std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()> >, std::reference_wrapper<bool> >(std::once_flag&, void (std::__future_base::_State_base::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, ...) at include/c++/4.7.0/mutex:819
    std::promise<void>::set_value (this=0x7fff32587880) at include/c++/4.7.0/future:1206
    foo::run (this=0x938320, p=...) at main.cpp:26

Ich bin mir ziemlich sicher, dass ich in meinem Code nichts falsch mache, oder?

Ist dies ein Problem mit der pthread-Implementierung, oder die std::future/std::promise-Implementierung?

Meine Bibliotheksversionen sind:

libstdc++.so.6
libc.so.6 (GNU C Library stable release version 2.11.1 (20100118))
libpthread.so.0 (Native POSIX Threads Library by Ulrich Drepper et al Copyright (C) 2006)

8voto

rodrigo Punkte 87935

In der Tat gibt es eine Wettlaufsituation zwischen dem Destruktor der lokalen promise Objekt (am Ende des Konstruktors und des Aufrufs von set_value() aus dem Thema. Das ist, set_value() weckt die Haupttreppe, die gleich darauf das Versprechensobjekt zerstört, aber die set_value() Funktion noch nicht beendet ist und sich festfährt.

Wenn ich den C++11-Standard lese, bin ich mir nicht sicher, ob Ihre Verwendung erlaubt ist:

void promise<void>::set_value();

Wirkung: speichert den Wert r atomar im gemeinsamen Zustand und macht diesen Zustand bereit.

Aber irgendwo anders:

Die Mitgliedsfunktionen set_value, set_exception, set_value_at_thread_exit und set_exception_at_thread_exit verhalten sich so, als ob sie eine einzelner Mutex die mit dem Versprechensobjekt verbunden sind, während das Versprechensobjekt aktualisiert wird.

Sind set_value() Aufrufen in Bezug auf andere Funktionen, wie z. B. den Destruktor, atomar sein?

IMHO würde ich nein sagen. Die Auswirkungen wären vergleichbar mit der Zerstörung eines Mutex, während ein anderer Thread ihn noch sperrt. Das Ergebnis ist undefiniert.

Die Lösung wäre, dass man p den Faden überleben. Ich kann mir zwei Lösungen vorstellen:

  1. Machen Sie p ein Mitglied der Klasse, so wie es Michael Burr in der anderen Antwort vorgeschlagen hat.

  2. Verschieben Sie das Versprechen in das Thema.

Im Konstruktor:

std::promise<void> p;
std::future <void> f = p.get_future();
_thread = std::thread(&foo::run, this, std::move(p));

Übrigens, Sie brauchen den Aufruf von bind (der Thread-Konstruktor ist bereits überladen), oder der Aufruf von std::move um den Faden zu verschieben (der richtige Wert ist bereits ein r-Wert). Der Aufruf von std::move in das Versprechen ist jedoch obligatorisch.

Und die Thread-Funktion erhält keine Referenz, sondern das verschobene Versprechen:

void run(std::promise<void> p)
{
    p.set_value();
}

Ich denke, dass dies genau der Grund ist, warum C++11 zwei verschiedene Klassen definiert: promise y future : Sie verschieben das Versprechen in den Thread, aber Sie behalten die Zukunft, um das Ergebnis wiederherzustellen.

5voto

Michael Burr Punkte 320591

Versuchen Sie, die std::promise<void> p; so dass es nicht mehr lokal im Konstruktor, sondern als Mitglied von struct foo :

struct foo
{
    foo(std::atomic<int>& count)
        : _stop(false)
    {
        // std::promise<void> p;    // <-- moved to be a member
        std::future <void> f = p.get_future();

        // ...same as before...
    }
    void run(std::promise<void>& p)
    {
        // ... same ...
    }

    std::promise<void> p;   // <---
    std::thread _thread;
    bool _stop;
};

Ich glaube, es kann sein, dass Sie in ein Rennen geraten, bei dem p wird im Konstruktor zerstört, während p.set_value() auf den Verweis auf diesen promise . Etwas geschieht im Inneren set_value() während der Beendigung/Aufräumarbeiten; durch den Verweis auf die bereits zerstörte std::promise korrumpiert irgendeinen Zustand in der pthread Bibliothek.

Dies ist reine Spekulation - ich habe im Moment keinen Zugang zu einem System, das das Problem reproduziert. Aber machen p ein Mitglied wird sicherstellen, dass seine Lebensdauer weit über den Abschluss des set_value() anrufen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X