wake-up-neo.com

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

Stimmt es, dass C++ 0x ohne Semaphoren kommt? Es gibt bereits einige Fragen zum Stack Overflow bezüglich der Verwendung von Semaphoren. Ich benutze sie die ganze Zeit (Posix-Semaphoren), 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 kann nicht garantiert werden, dass Thread1 den Mutex zuerst sperrt.

Also, da Boost auch keine Semaphore hat, was ist der einfachste Weg, um das Obige zu erreichen?

120
tauran

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

#include <mutex>
#include <condition_variable>

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

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

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

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

Basierend auf Maxim Yegorushkins Antwort 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;
};
98
Tsuneo Yoshioka

Ich habe mich dazu entschlossen, das robusteste/allgemeinste C++ 11-Semaphor zu schreiben, das ich konnte, im Stil des Standards, so viel ich konnte (Anmerkung using semaphore = ..., Sie würden normalerweise nur den Namen semaphore verwenden, ähnlich wie bei der normalen Verwendung von 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();
}
36
David

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 bevorzuge die Verwendung eines Synchronisationsmechanismus auf einer geeigneten Abstraktionsebene, anstatt immer eine zusammengefügte Version mit einfacheren Operatoren einzufügen.

14
Michael Zillich

Sie können auch cpp11-on-multicore auschecken - es hat eine portable und optimale Semaphor-Implementierung.

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

8
onqtam

Sie können mit Mutex- und Bedingungsvariablen arbeiten. Sie erhalten exklusiven Zugriff mit dem Mutex, prüfen, ob Sie fortfahren möchten oder auf das andere Ende warten müssen. Wenn Sie warten müssen, warten Sie in einem Zustand. Wenn der andere Thread feststellt, dass Sie fortfahren können, signalisiert er den Zustand.

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

Auch kann nützlich sein RAII Semaphore Wrapper in Threads:

class ScopedSemaphore
{
public:
    explicit ScopedSemaphore(Semaphore& sem) : m_Semaphore(sem) { m_Semaphore.Wait(); }
    ScopedSemaphore(const ScopedSemaphore&) = delete;
    ~ScopedSemaphore() { m_Semaphore.Notify(); }

   ScopedSemaphore& operator=(const ScopedSemaphore&) = delete;

private:
    Semaphore& m_Semaphore;
};

Anwendungsbeispiel in Multithread-App:

boost::ptr_vector<std::thread> threads;
Semaphore semaphore;

for (...)
{
    ...
    auto t = new std::thread([..., &semaphore]
    {
        ScopedSemaphore scopedSemaphore(semaphore);
        ...
    }
    );
    threads.Push_back(t);
}

for (auto& t : threads)
    t.join();
2
slasla

Ich fand die shared_ptr und weak_ptr, eine lange mit einer Liste, hat den Job, den ich brauchte. Mein Problem war, dass mehrere Kunden mit den internen Daten eines Hosts interagieren wollten. Normalerweise aktualisiert der Host die Daten selbst. Wenn ein Client dies jedoch anfordert, muss der Host die Aktualisierung stoppen, bis kein Client auf die Hostdaten zugreift. Gleichzeitig kann ein Client einen exklusiven Zugriff anfordern, sodass weder andere Clients noch der Host diese Hostdaten ändern können.

Wie ich das gemacht habe, habe ich eine Struktur erstellt:

struct UpdateLock
{
    typedef std::shared_ptr< UpdateLock > ptr;
};

Jeder Kunde hätte ein Mitglied von solchen:

UpdateLock::ptr m_myLock;

Dann hätte der Host ein weak_ptr-Mitglied für Exklusivität und eine Liste von weak_ptrs für nicht exklusive Sperren:

std::weak_ptr< UpdateLock > m_exclusiveLock;
std::list< std::weak_ptr< UpdateLock > > m_locks;

Es gibt eine Funktion zum Aktivieren der Sperre und eine weitere Funktion zum Überprüfen, ob der Host gesperrt ist:

UpdateLock::ptr LockUpdate( bool exclusive );       
bool IsUpdateLocked( bool exclusive ) const;

Ich teste LockUpdate, IsUpdateLocked und regelmäßig die Update-Routine des Hosts auf Sperren. Das Testen auf eine Sperre ist so einfach wie das Überprüfen, ob der Eintrag "weak_ptr" abgelaufen ist, und das Entfernen von abgelaufenen Einträgen aus der Liste "m_locks" (ich mache dies nur während des Host-Updates). Ich kann überprüfen, ob die Liste leer ist. Gleichzeitig erhalte ich eine automatische Entsperrung, wenn ein Client das shared_ptr zurücksetzt, an dem er sich aufhängt. Dies geschieht auch, wenn ein Client automatisch zerstört wird.

Der Gesamteffekt ist, da Clients selten Exklusivität benötigen (normalerweise nur für Hinzufügungen und Löschungen reserviert), dass eine Anfrage an LockUpdate (false), das heißt nicht exklusiv, meistens erfolgreich ist, solange (! M_exclusiveLock). Und ein LockUpdate (true), eine Anfrage nach Exklusivität, ist nur dann erfolgreich, wenn sowohl (! ​​M_exclusiveLock) als auch (m_locks.empty ()).

Eine Warteschlange könnte hinzugefügt werden, um zwischen exklusiven und nicht exklusiven Sperren einzugrenzen. Bisher hatte ich jedoch noch keine Kollisionen. Daher möchte ich abwarten, bis die Lösung hinzugefügt wird (meistens unter realen Testbedingungen).

Soweit funktioniert das gut für meine Bedürfnisse; Ich kann mir vorstellen, dass dies erweitert werden muss, und einige Probleme, die bei der erweiterten Verwendung auftreten können, waren jedoch schnell zu implementieren und erforderten nur sehr wenig benutzerdefinierten Code.

1
Kit10