C++0x hat keine Semaphoren? Wie synchronisiere ich Threads?

Lesezeit: 11 Minuten

C0x hat keine Semaphoren Wie synchronisiere ich Threads
Stier

Stimmt es, dass C++0x ohne Semaphore kommen wird? Es gibt bereits einige Fragen zu Stack Overflow bezüglich der Verwendung von Semaphoren. Ich benutze sie (Posix-Semaphoren) die ganze Zeit, um einen Thread auf ein Ereignis in einem anderen Thread warten zu lassen:

void thread0(...)
{
  doSomething0();

  event1.wait();

  ...
}

void thread1(...)
{
  doSomething1();

  event1.post();

  ...
}

Wenn ich das mit einem Mutex machen würde:

void thread0(...)
{
  doSomething0();

  event1.lock(); event1.unlock();

  ...
}

void thread1(...)
{
  event1.lock();

  doSomethingth1();

  event1.unlock();

  ...
}

Problem: Es ist hässlich und es ist nicht garantiert, dass Thread1 den Mutex zuerst sperrt (da derselbe Thread einen Mutex sperren und entsperren sollte, können Sie event1 auch nicht sperren, bevor Thread0 und Thread1 gestartet wurden).

Da Boost auch keine Semaphoren hat, was ist der einfachste Weg, um das oben Genannte zu erreichen?

  • Verwenden Sie vielleicht die Bedingung mutex und std::promise und std::future?

    – Yves

    13. Dezember 2016 um 8:22 Uhr

C0x hat keine Semaphoren Wie synchronisiere ich Threads
Maxim Egorushkin

Sie können ganz einfach einen aus einem Mutex und einer Bedingungsvariablen erstellen:

#include <mutex>
#include <condition_variable>

class semaphore {
    std::mutex mutex_;
    std::condition_variable condition_;
    unsigned long count_ = 0; // Initialized as locked.

public:
    void release() {
        std::lock_guard<decltype(mutex_)> lock(mutex_);
        ++count_;
        condition_.notify_one();
    }

    void acquire() {
        std::unique_lock<decltype(mutex_)> lock(mutex_);
        while(!count_) // Handle spurious wake-ups.
            condition_.wait(lock);
        --count_;
    }

    bool try_acquire() {
        std::lock_guard<decltype(mutex_)> lock(mutex_);
        if(count_) {
            --count_;
            return true;
        }
        return false;
    }
};

  • jemand sollte dem Normenausschuss einen Vorschlag unterbreiten

    Benutzer90843

    6. Juni 2012 um 23:36 Uhr


  • Ein Kommentar hier, der mich anfangs verwirrt hat, ist die Sperre in der Warteschleife. Man könnte sich fragen, wie ein Thread daran vorbeikommen kann, um zu benachrichtigen, wenn die Sperre durch Warten gehalten wird? Die etwas schlecht undeutlich dokumentierte Antwort ist, dass condition_variable.wait die Sperre pulsiert und es einem anderen Thread ermöglicht, auf atomare Weise an der Benachrichtigung vorbeizukommen, zumindest verstehe ich das so

    Benutzer90843

    14. Juni 2012 um 6:53 Uhr


  • Es war absichtlich von Boost ausgeschlossen, da ein Semaphor zu viel Strick ist, an dem sich Programmierer aufhängen können. Bedingungsvariablen sind angeblich besser handhabbar. Ich verstehe ihren Standpunkt, fühle mich aber ein bisschen bevormundet. Ich gehe davon aus, dass die gleiche Logik für C++11 gilt – von Programmierern wird erwartet, dass sie ihre Programme so schreiben, dass „natürlich“ Condvars oder andere anerkannte Synchronisationstechniken verwendet werden. Supply a semaphor würde dagegen laufen, unabhängig davon, ob es auf condvar oder nativ implementiert ist.

    – Steve Jessop

    31. August 2012 um 15:31 Uhr

  • Hinweis – Siehe en.wikipedia.org/wiki/Spurious_wakeup für die Begründung hinter der while(!count_) Schleife.

    – Dan Nissenbaum

    16. November 2012 um 7:49 Uhr

  • @ Maxim Es tut mir leid, ich glaube nicht, dass du Recht hast. sem_wait und sem_post syscall nur bei Konflikten (check sourceware.org/git/?p=glibc.git;a=blob;f=nptl/sem_wait.c ), sodass der Code hier die libc-Implementierung mit potenziellen Fehlern dupliziert. Wenn Sie Portabilität auf einem beliebigen System beabsichtigen, könnte dies eine Lösung sein, aber wenn Sie nur Posix-Kompatibilität benötigen, verwenden Sie Posix-Semaphore.

    – xryl669

    5. August 2014 um 15:58 Uhr


1647067811 774 C0x hat keine Semaphoren Wie synchronisiere ich Threads
Tsuneo Yoshioka

Basierend auf der Antwort von Maxim Yegorushkin habe ich versucht, das Beispiel im C++ 11-Stil zu erstellen.

#include <mutex>
#include <condition_variable>

class Semaphore {
public:
    Semaphore (int count_ = 0)
        : count(count_) {}

    inline void notify()
    {
        std::unique_lock<std::mutex> lock(mtx);
        count++;
        cv.notify_one();
    }

    inline void wait()
    {
        std::unique_lock<std::mutex> lock(mtx);

        while(count == 0){
            cv.wait(lock);
        }
        count--;
    }

private:
    std::mutex mtx;
    std::condition_variable cv;
    int count;
};

  • Sie können wait() auch zu einem Dreizeiler machen: cv.wait(lck, [this]() { return count > 0; });

    – Domi

    6. Dezember 2013 um 13:14 Uhr

  • Das Hinzufügen einer weiteren Klasse im Sinne von lock_guard ist ebenfalls hilfreich. In RAII-Manier ruft der Konstruktor, der die Semaphore als Referenz nimmt, den wait()-Aufruf der Semaphore auf, und der Destruktor ruft seinen Notify()-Aufruf auf. Dadurch wird verhindert, dass Ausnahmen das Semaphor nicht freigeben.

    – Jim Hunziker

    24. Oktober 2014 um 16:58 Uhr

  • Gibt es keinen Deadlock, wenn sagen wir N Threads namens wait() und count==0, dann cv.notify_one(); wird nie aufgerufen, da der mtx noch nicht erschienen ist?

    – Marcello

    18. Mai 2015 um 21:26 Uhr

  • @Marcello Die wartenden Threads halten die Sperre nicht. Der ganze Sinn von Bedingungsvariablen besteht darin, eine atomare “Entsperr- und Warte”-Operation bereitzustellen.

    – David Schwartz

    19. Oktober 2015 um 21:39 Uhr

  • Sie sollten die Sperre freigeben, bevor Sie notification_one() aufrufen, um das Aufwecken nicht sofort zu blockieren … siehe hier: en.cppreference.com/w/cpp/thread/condition_variable/notify_all

    – kylefinn

    20. Dezember 2019 um 18:31 Uhr

1647067811 17 C0x hat keine Semaphoren Wie synchronisiere ich Threads
David

Ich habe mich entschieden, das robusteste/generischste C++11-Semaphor zu schreiben, das ich konnte, im Stil des Standards so weit wie möglich (Anmerkung using semaphore = ...würden Sie normalerweise nur den Namen verwenden semaphore ähnlich wie bei normaler Verwendung string nicht basic_string):

template <typename Mutex, typename CondVar>
class basic_semaphore {
public:
    using native_handle_type = typename CondVar::native_handle_type;

    explicit basic_semaphore(size_t count = 0);
    basic_semaphore(const basic_semaphore&) = delete;
    basic_semaphore(basic_semaphore&&) = delete;
    basic_semaphore& operator=(const basic_semaphore&) = delete;
    basic_semaphore& operator=(basic_semaphore&&) = delete;

    void notify();
    void wait();
    bool try_wait();
    template<class Rep, class Period>
    bool wait_for(const std::chrono::duration<Rep, Period>& d);
    template<class Clock, class Duration>
    bool wait_until(const std::chrono::time_point<Clock, Duration>& t);

    native_handle_type native_handle();

private:
    Mutex   mMutex;
    CondVar mCv;
    size_t  mCount;
};

using semaphore = basic_semaphore<std::mutex, std::condition_variable>;

template <typename Mutex, typename CondVar>
basic_semaphore<Mutex, CondVar>::basic_semaphore(size_t count)
    : mCount{count}
{}

template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::notify() {
    std::lock_guard<Mutex> lock{mMutex};
    ++mCount;
    mCv.notify_one();
}

template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::wait() {
    std::unique_lock<Mutex> lock{mMutex};
    mCv.wait(lock, [&]{ return mCount > 0; });
    --mCount;
}

template <typename Mutex, typename CondVar>
bool basic_semaphore<Mutex, CondVar>::try_wait() {
    std::lock_guard<Mutex> lock{mMutex};

    if (mCount > 0) {
        --mCount;
        return true;
    }

    return false;
}

template <typename Mutex, typename CondVar>
template<class Rep, class Period>
bool basic_semaphore<Mutex, CondVar>::wait_for(const std::chrono::duration<Rep, Period>& d) {
    std::unique_lock<Mutex> lock{mMutex};
    auto finished = mCv.wait_for(lock, d, [&]{ return mCount > 0; });

    if (finished)
        --mCount;

    return finished;
}

template <typename Mutex, typename CondVar>
template<class Clock, class Duration>
bool basic_semaphore<Mutex, CondVar>::wait_until(const std::chrono::time_point<Clock, Duration>& t) {
    std::unique_lock<Mutex> lock{mMutex};
    auto finished = mCv.wait_until(lock, t, [&]{ return mCount > 0; });

    if (finished)
        --mCount;

    return finished;
}

template <typename Mutex, typename CondVar>
typename basic_semaphore<Mutex, CondVar>::native_handle_type basic_semaphore<Mutex, CondVar>::native_handle() {
    return mCv.native_handle();
}

  • Das funktioniert, mit einer kleinen Änderung. Die wait_for und wait_until Methodenaufrufe mit dem Prädikat geben einen booleschen Wert zurück (kein `std::cv_status).

    – jdknight

    4. März 2015 um 20:17 Uhr

  • Entschuldigung, dass ich so spät im Spiel pingelig bin. std::size_t ist vorzeichenlos, also ist das Dekrementieren unter Null UB, und das wird es immer sein >= 0. meiner bescheidenen Meinung nach count sollte ein sein int.

    – Richard Hodges

    29. November 2015 um 18:44 Uhr

  • @RichardHodges Es gibt keine Möglichkeit, unter Null zu dekrementieren, also gibt es kein Problem, und was würde eine negative Zählung auf einem Semaphor bedeuten? Das macht IMO gar keinen Sinn.

    – David

    1. Dezember 2015 um 15:39 Uhr


  • @David Was wäre, wenn ein Thread darauf warten müsste, dass andere Dinge initialisieren? Wenn zum Beispiel 1 Leser-Thread auf 4 Threads warten soll, würde ich den Semaphor-Konstruktor mit -3 aufrufen, damit der Leser-Thread wartet, bis alle anderen Threads einen Beitrag erstellt haben. Ich denke, es gibt andere Möglichkeiten, das zu tun, aber ist es nicht vernünftig? Ich denke, es ist tatsächlich die Frage, die das OP stellt, aber mit mehr “Thread1”.

    – jmmut

    20. Juni 2016 um 13:48 Uhr

  • @RichardHodges, um sehr pedantisch zu sein, das Dekrementieren eines vorzeichenlosen Integer-Typs unter 0 ist nicht UB.

    – jcai

    2. Juni 2017 um 19:29 Uhr

in Übereinstimmung mit Posix-Semaphoren, würde ich hinzufügen

class semaphore
{
    ...
    bool trywait()
    {
        boost::mutex::scoped_lock lock(mutex_);
        if(count_)
        {
            --count_;
            return true;
        }
        else
        {
            return false;
        }
    }
};

Und ich ziehe es vor, einen Synchronisationsmechanismus auf einer bequemen Abstraktionsebene zu verwenden, anstatt immer eine zusammengesetzte Version mit einfacheren Operatoren zu kopieren und einzufügen.

1647067812 54 C0x hat keine Semaphoren Wie synchronisiere ich Threads
einpoklum

C++20 hat endlich Semaphoren – std::counting_semaphore<max_count>.

Diese haben (mindestens) folgende Methoden:

  • acquire() (Blockierung)
  • try_acquire() (nicht blockierend, kehrt sofort zurück)
  • try_acquire_for() (nicht blockierend, dauert eine Dauer)
  • try_acquire_until() (nicht blockierend, dauert eine Weile, bis der Versuch beendet wird)
  • release()

Du kannst lesen diese CppCon 2019 Präsentationsfolienoder sehen Sie sich die an Video. Es gibt auch den offiziellen Vorschlag P0514R4aber es ist möglicherweise nicht auf dem neuesten Stand von C++20.

  • @Sandburg: Meines Wissens nach tut es das.

    – einpoklum

    15. Januar 2021 um 13:21 Uhr


  • Okay, ja include <semaphore> schwer zu finden … viel “Boost” -Geräusch zu diesem Thema.

    – Sandburg

    15. Januar 2021 um 14:54 Uhr


  • @Sandburg: Ich habe einen Link gepostet. Sie können sich auch die Wikipedia-Seite zu C++20 ansehen.

    – einpoklum

    15. Januar 2021 um 16:33 Uhr

  • Ich kann die Semaphor-Header-Datei nirgendwo finden. Irgendwelche Vorschläge?

    – Vishaal Selvaraj

    26. Januar 2021 um 7:24 Uhr

  • @VishaalSelvaraj: 1. Durchsuchen Sie die Bibliothekspaketdateien. 2. Verwenden Sie eine Suchmaschine, um zu überprüfen, ob andere dieses Problem festgestellt haben. Stellen Sie hier eine weitere Frage zu SO und stellen Sie sicher, dass Sie alle Details angeben (Betriebssystemverteilung, Compiler, Standardbibliothek usw.).

    – einpoklum

    26. Januar 2021 um 8:37 Uhr

1647067812 640 C0x hat keine Semaphoren Wie synchronisiere ich Threads
onqtam

Sie können auch auschecken cpp11-auf-Multicore – es hat eine portable und optimale Semaphor-Implementierung.

Das Repository enthält auch andere Threading-Goodies, die das C++11-Threading ergänzen.

  • @Sandburg: Meines Wissens nach tut es das.

    – einpoklum

    15. Januar 2021 um 13:21 Uhr


  • Okay, ja include <semaphore> schwer zu finden … viel “Boost” -Geräusch zu diesem Thema.

    – Sandburg

    15. Januar 2021 um 14:54 Uhr


  • @Sandburg: Ich habe einen Link gepostet. Sie können sich auch die Wikipedia-Seite zu C++20 ansehen.

    – einpoklum

    15. Januar 2021 um 16:33 Uhr

  • Ich kann die Semaphor-Header-Datei nirgendwo finden. Irgendwelche Vorschläge?

    – Vishaal Selvaraj

    26. Januar 2021 um 7:24 Uhr

  • @VishaalSelvaraj: 1. Durchsuchen Sie die Bibliothekspaketdateien. 2. Verwenden Sie eine Suchmaschine, um zu überprüfen, ob andere dieses Problem festgestellt haben. Stellen Sie hier eine weitere Frage zu SO und stellen Sie sicher, dass Sie alle Details angeben (Betriebssystemverteilung, Compiler, Standardbibliothek usw.).

    – einpoklum

    26. Januar 2021 um 8:37 Uhr

1647067813 806 C0x hat keine Semaphoren Wie synchronisiere ich Threads
David Rodríguez – Dribeas

Sie können mit Mutex- und Bedingungsvariablen arbeiten. Mit dem Mutex erhalten Sie exklusiven Zugriff, prüfen, ob Sie weitermachen wollen oder auf das andere Ende warten müssen. Wenn Sie warten müssen, warten Sie in einer Bedingung. Wenn der andere Thread feststellt, dass Sie fortfahren können, signalisiert er die Bedingung.

Es gibt einen Kurzschluss Beispiel in der boost::thread-Bibliothek, die Sie höchstwahrscheinlich einfach kopieren können (die C++0x- und boost-Thread-Bibliotheken sind sehr ähnlich).

  • Bedingung signalisiert nur wartenden Threads, oder nicht? Also, wenn Thread0 nicht da ist und wartet, wenn Thread1 signalisiert, dass er später blockiert wird? Plus: Ich brauche das zusätzliche Schloss, das mit der Bedingung kommt, nicht – es ist Overhead.

    – Stier

    25. Januar 2011 um 10:49 Uhr


  • Ja, Bedingung signalisiert nur wartende Threads. Das übliche Muster besteht darin, eine Variable mit dem Status und einer Bedingung für den Fall zu haben, dass Sie warten müssen. Denken Sie an einen Producer/Consumer, es wird eine Zählung der Elemente im Puffer geben, der Producer sperrt, fügt das Element hinzu, erhöht die Zählung und signalisiert. Der Verbraucher sperrt, überprüft den Zähler und verbraucht, wenn er nicht Null ist, während er in der Bedingung wartet, wenn er Null ist.

    – David Rodríguez – Dribeas

    25. Januar 2011 um 11:13 Uhr

  • So simulieren Sie eine Semaphore: Initialisieren Sie eine Variable mit dem Wert, den Sie dann der Semaphore geben würden wait() wird übersetzt in “Sperren, Zählwert prüfen, wenn nicht Null, dekrementieren und fortfahren; wenn Null auf Bedingung warten” while post wäre “Sperren, Zähler erhöhen, Signal wenn 0”

    – David Rodríguez – Dribeas

    25. Januar 2011 um 11:17 Uhr

  • Ja, klingt gut. Ich frage mich, ob Posix-Semaphoren auf die gleiche Weise implementiert werden.

    – Stier

    25. Januar 2011 um 11:25 Uhr

  • @tauran: Ich weiß es nicht genau (und es könnte davon abhängen, welches Posix-Betriebssystem), aber ich halte es für unwahrscheinlich. Semaphore sind traditionell ein Synchronisationselement auf “niedrigerer Ebene” als Mutexe und Bedingungsvariablen und können im Prinzip effizienter gemacht werden, als wenn sie auf einer Condvar implementiert würden. In einem bestimmten Betriebssystem ist es also wahrscheinlicher, dass alle Synchronisierungsprimitive auf Benutzerebene auf einigen gängigen Tools aufbauen, die mit dem Scheduler interagieren.

    – Steve Jessop

    31. August 2012 um 15:28 Uhr


992950cookie-checkC++0x hat keine Semaphoren? Wie synchronisiere ich Threads?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy