C++0x 没有信号量?如何同步线程?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4792449/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 16:31:29  来源:igfitidea点击:

C++0x has no semaphores? How to synchronize threads?

c++multithreadingsynchronizationc++11boost-thread

提问by tauran

Is it true that C++0x will come without semaphores? There are already some questions on Stack Overflow regarding the use of semaphores. I use them (posix semaphores) all the time to let a thread wait for some event in another thread:

C++0x 真的没有信号量吗?Stack Overflow 上已经有一些关于信号量使用的问题。我一直使用它们(posix 信号量)让一个线程等待另一个线程中的某个事件:

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

  event1.wait();

  ...
}

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

  event1.post();

  ...
}

If I would do that with a mutex:

如果我用互斥锁来做到这一点:

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

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

  ...
}

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

  doSomethingth1();

  event1.unlock();

  ...
}

Problem: It's ugly and it's not guaranteed that thread1 locks the mutex first (Given that the same thread should lock and unlock a mutex, you also can't lock event1 before thread0 and thread1 started).

问题:这很丑陋,并且不能保证线程 1 首先锁定互斥锁(假设同一个线程应该锁定和解锁互斥锁,您也不能在线程 0 和线程 1 启动之前锁定 event1)。

So since boost doesn't have semaphores either, what is the simplest way to achieve the above?

因此,既然 boost 也没有信号量,那么实现上述目标的最简单方法是什么?

回答by Maxim Egorushkin

You can easily build one from a mutex and a condition variable:

您可以轻松地从互斥锁和条件变量构建一个:

#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;
    }
};

回答by Tsuneo Yoshioka

Based on Maxim Yegorushkin's answer, I tried to make the example in C++11 style.

根据Maxim Yegorushkin 的回答,我尝试以 C++11 风格制作示例。

#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;
};

回答by David

I decided to write the most robust/generic C++11 semaphore I could, in the style of the standard as much as I could (note using semaphore = ..., you normally would just use the name semaphoresimilar to normally using stringnot basic_string):

我决定尽可能地以标准的风格编写我所能编写的最健壮/通用的 C++11 信号量(注意using semaphore = ...,您通常只会使用semaphore类似于通常使用stringnot的名称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();
}

回答by Michael Zillich

in acordance with posix semaphores, I would add

根据posix信号量,我会添加

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

And I much prefer using a synchronisation mechanism at a convenient level of abstraction, rather than always copy pasting a stitched-together version using more basic operators.

而且我更喜欢在方便的抽象级别使用同步机制,而不是总是使用更基本的运算符复制粘贴拼接在一起的版本。

回答by onqtam

You can also check out cpp11-on-multicore- it has a portable and optimal semaphore implementation.

您还可以查看cpp11-on-multicore- 它具有可移植且最佳的信号量实现。

The repository also contains other threading goodies that complement c++11 threading.

该存储库还包含补充 c++11 线程的其他线程好东西。

回答by David Rodríguez - dribeas

You can work with mutex and condition variables. You gain exclusive access with the mutex, check whether you want to continue or need to wait for the other end. If you need to wait, you wait in a condition. When the other thread determines that you can continue, it signals the condition.

您可以使用互斥锁和条件变量。您获得互斥体的独占访问权,检查您是要继续还是需要等待另一端。如果你需要等待,你就在一个条件中等待。当另一个线程确定您可以继续时,它会发出条件信号。

There is a short examplein the boost::thread library that you can most probably just copy (the C++0x and boost thread libs are very similar).

boost::thread 库中有一个简短的示例,您很可能只需复制它即可(C++0x 和 boost 线程库非常相似)。

回答by slasla

Also can be useful RAII semaphore wrapper in threads:

也可以是线程中有用的 RAII 信号量包装器:

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;
};

Usage example 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();

回答by Kit10

I found the shared_ptr and weak_ptr, a long with a list, did the job I needed. My issue was, I had several clients wanting to interact with a host's internal data. Typically, the host updates the data on it's own, however, if a client requests it, the host needs to stop updating until no clients are accessing the host data. At the same time, a client could ask for exclusive access, so that no other clients, nor the host, could modify that host data.

我发现 shared_ptr 和weak_ptr,一个很长的列表,完成了我需要的工作。我的问题是,我有几个客户想要与主机的内部数据进行交互。通常,主机自行更新数据,但是,如果客户端请求它,则主机需要停止更新,直到没有客户端访问主机数据。同时,客户端可以请求独占访问,因此其他客户端和主机都不能修改该主机数据。

How I did this was, I created a struct:

我是如何做到这一点的,我创建了一个结构:

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

Each client would have a member of such:

每个客户都有这样的成员:

UpdateLock::ptr m_myLock;

Then the host would have a weak_ptr member for exclusivity, and a list of weak_ptrs for non-exclusive locks:

然后主机会有一个weak_ptr成员用于排他性,以及一个weak_ptr列表用于非排他锁:

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

There is a function to enable locking, and another function to check if the host is locked:

有一个功能可以启用锁定,还有另一个功能可以检查主机是否被锁定:

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

I test for locks in LockUpdate, IsUpdateLocked, and periodically in the host's Update routine. Testing for a lock is as simple as checking if the weak_ptr's expired, and removing any expired from the m_locks list (I only do this during the host update), I can check if the list is empty; at the same time, I get automatic unlocking when a client resets the shared_ptr they are hanging onto, which also happens when a client gets destroyed automatically.

我在 LockUpdate、IsUpdateLocked 和主机的 Update 例程中定期测试锁。测试锁就像检查weak_ptr 是否过期一样简单,并从m_locks 列表中删除任何过期(我只在主机更新期间这样做),我可以检查列表是否为空;同时,当客户端重置他们挂在的 shared_ptr 时,我会自动解锁,当客户端被自动销毁时也会发生这种情况。

The over all effect is, since clients rarely need exclusivity (typically reserved for additions and deletions only), most of the time a request to LockUpdate( false ), that is to say non-exclusive, succeeds so long as (! m_exclusiveLock). And a LockUpdate( true ), a request for exclusivity, succeeds only when both (! m_exclusiveLock) and (m_locks.empty()).

总体效果是,由于客户端很少需要独占性(通常仅保留用于添加和删除),大多数情况下,对 LockUpdate( false ) 的请求,即非独占性,只要 (!m_exclusiveLock) 就会成功。而一个 LockUpdate(true),一个独占请求,只有在 (!m_exclusiveLock) 和 (m_locks.empty()) 时才会成功。

A queue could be added to mitigate between exclusive and non-exclusive locks, however, I have had no collisions thus far, so I intend to wait until that happens to add the solution (mostly so I have a real-world test condition).

可以添加一个队列来缓解排他锁和非排他锁之间的冲突,但是,到目前为止我还没有发生冲突,所以我打算等到这种情况发生时添加解决方案(主要是因为我有一个真实世界的测试条件)。

So far this is working well for my needs; I can imagine the need to expand this, and some issues that might arise over expanded use, however, this was quick to implement, and required very little custom code.

到目前为止,这很好地满足了我的需求;我可以想象需要扩展它,以及扩展使用可能会出现的一些问题,但是,这实现起来很快,并且只需要很少的自定义代码。

回答by einpoklum

C++20 will finally have semaphores - std::counting_semaphore<max_count>.

C++20 最终会有信号量 - std::counting_semaphore<max_count>

These will have (at least) the following methods:

这些将具有(至少)以下方法:

  • acquire()(blocking)
  • try_acquire()(non-blocking, returns immediatel)
  • try_acquire_for()(non-blocking, takes a duration)
  • try_acquire_until()(non-blocking, takes a time at which to stop trying)
  • release()
  • acquire()(阻塞)
  • try_acquire()(非阻塞,立即返回)
  • try_acquire_for()(非阻塞,需要一段时间)
  • try_acquire_until()(非阻塞,需要一段时间才能停止尝试)
  • release()

This isn't listed on cppreference yet, but you can read these CppCon 2019 presentation slides, or watch the video. There's also the official proposal P0514R4, but I'm not sure that's the most up-to-date version.

这尚未在 cppreference 中列出,但您可以阅读这些 CppCon 2019 演示幻灯片,或观看视频。还有官方提案P0514R4,但我不确定这是最新版本。

回答by Jeffery

In case someone is interested in the atomic version, here is the implementation. The performance is expected better than the mutex & condition variable version.

如果有人对原子版本感兴趣,这里是实现。性能预计优于互斥锁和条件变量版本。

class semaphore_atomic
{
public:
    void notify() {
        count_.fetch_add(1, std::memory_order_release);
    }

    void wait() {
        while (true) {
            int count = count_.load(std::memory_order_relaxed);
            if (count > 0) {
                if (count_.compare_exchange_weak(count, count-1, std::memory_order_acq_rel, std::memory_order_relaxed)) {
                    break;
                }
            }
        }
    }

    bool try_wait() {
        int count = count_.load(std::memory_order_relaxed);
        if (count > 0) {
            if (count_.compare_exchange_strong(count, count-1, std::memory_order_acq_rel, std::memory_order_relaxed)) {
                return true;
            }
        }
        return false;
    }
private:
    std::atomic_int count_{0};
};