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

tau*_*ran 124 c++ multithreading synchronization boost-thread c++11

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

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

  event1.wait();

  ...
}

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

  event1.post();

  ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  ...
}

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

  doSomethingth1();

  event1.unlock();

  ...
}
Run Code Online (Sandbox Code Playgroud)

问题:它很难看并且不能保证thread1首先锁定互斥锁(假设同一个线程应该锁定和解锁互斥锁,你也无法在thread0和thread1启动之前锁定event1).

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

Max*_*kin 171

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

#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;
    }
};
Run Code Online (Sandbox Code Playgroud)

  • 有人应该向标准委员会提交提案 (93认同)
  • 故意将*排除在Boost之外,因为信号量太大,程序员无法自拔.据推测,条件变量更易于管理.我看到他们的观点,但感到有点光顾.我假设相同的逻辑适用于C++ 11 - 程序员应该以"自然地"使用condvars或其他批准的同步技术的方式编写程序.无论是在condvar之上还是在本地实现,信号量都会与之相反. (28认同)
  • 这里的评论最让我困惑的是锁等待,有人可能会问,如果等待锁定,线程怎么能通过通知?有点模糊记录的答案是,condition_variable.wait会使锁定脉冲,允许另一个线程以原子方式通过通知,至少这是我理解它的方式 (6认同)
  • 注 - 请参阅http://en.wikipedia.org/wiki/Spurious_wakeup了解`while(!count_)`循环背后的基本原理. (3认同)
  • @Maxim我很抱歉,我认为你不对.sem_wait和sem_post也只有争用的syscall(检查https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/sem_wait.c)所以这里的代码最终会复制libc实现,有潜在的错误.如果您打算在任何系统上实现可移植性,那么它可能是一个解决方案,但如果您只需要Posix兼容性,请使用Posix信号量. (3认同)
  • http://pubs.opengroup.org/onlinepubs/009696799/functions/pthread_cond_signal.html:_线程可以调用pthread_cond_broadcast()或pthread_cond_signal()函数,无论它当前是否拥有调用pthread_cond_wait()的线程的互斥锁,或者pthread_cond_timedwait()在等待期间与条件变量相关联; 但是,如果需要可预测的调度行为,则该互斥锁应由调用pthread_cond_broadcast()或pthread_cond_signal()的线程锁定. (2认同)
  • 不确定这是否是最佳解决方案。对变量的任何访问都需要锁定,即使没有争用也是如此。操作系统提供比这更有效的底层原语,使用原子 CAS,每次出现争用时都不会锁定。 (2认同)
  • @user90843 8 年后,现在是 C++20 的 `std::counting_semaphore`。 (2认同)

Tsu*_*oka 101

根据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;
};
Run Code Online (Sandbox Code Playgroud)

  • 你可以使wait()也成为一个三线程:`cv.wait(lck,[this](){return count> 0;});` (30认同)
  • 您应该在调用notify_one()之前释放锁以避免立即阻塞唤醒...请参见此处:https://en.cppreference.com/w/cpp/thread/condition_variable/notify_all (4认同)
  • 本着lock_guard的精神添加另一个类也很有帮助。以RAII方式,以信号量为参考的构造函数将调用信号量的wait()调用,而析构函数将调用其notify()调用。这样可以防止异常无法释放信号量。 (2认同)

Dav*_*vid 38

我决定像我一样能写出最强大的/通用的C++ 11旗语我可以,在标准样式(注意using semaphore = ...,你通常只会使用的名称semaphore类似的正常使用string没有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();
}
Run Code Online (Sandbox Code Playgroud)

  • @RichardHodges没有办法减少到零以下,所以没有问题,信号量的负数是什么意思?这甚至没有理由IMO. (3认同)
  • @RichardHodges 非常迂腐,将无符号整数类型递减到 0 以下不是 UB。 (3认同)

小智 15

根据posix信号量,我想补充一下

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

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


ein*_*ica 11

C++20 终于有了信号量 - std::counting_semaphore<max_count>

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

  • acquire() (阻塞)
  • try_acquire() (非阻塞,立即返回)
  • try_acquire_for() (非阻塞,需要一段时间)
  • try_acquire_until() (非阻塞,需要一段时间才能停止尝试)
  • release()

您可以阅读这些 CppCon 2019 演示幻灯片,或观看视频。还有官方提案P0514R4,但它可能不是最新的实际 C++20。

  • @Sandburg:据我所知,确实如此。 (2认同)

onq*_*tam 9

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

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


Dav*_*eas 7

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

boost :: thread库中有一个简短的例子,你最有可能只是复制(C++ 0x和boost线程库非常相似).

  • 你可以用这种方式模拟一个信号量:用你给信号量的值初始化一个变量,然后`wait()`被转换为"锁定,检查计数是否非零递减并继续;如果零等待条件"同时`post`将是"锁定,增量计数器,如果它是0则发出信号" (2认同)