C++并发编程 等待与唤醒

C++并发编程 等待与唤醒

条件变量

条件变量, 包括(std::condition_variable 和 std::condition_variable_any)
  定义在 condition_variable 头文件中, 它们都需要与互斥量(作为同步工具)一起才能工作.

  std::condition_variable 允许阻塞一个线程, 直到条件达成.

成员函数

void wait(std::unique_lock<std::mutex>& lock);
等待, 通过 notify_one(), notify_all()或伪唤醒结束等待
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
等待, 通过 notify_one(), notify_all()被调用, 并且谓词为 true 时结束等待.
pred 谓词必须是合法的, 并且需要返回一个值, 这个值可以和bool互相转化
cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& absolute_time);
调用 notify_one(), notify_all(), 超时或线程伪唤醒时, 结束等待.
返回值标识了是否超时.
bool wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& absolute_time, Predicate pred);
等待, 通过 notify_one(), notify_all(), 超时, 线程伪唤醒, 并且谓词为 true 时结束等待.
cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& relative_time);
调用 notify_one(), notify_all(), 指定时间内达成条件或线程伪唤醒时,结束等待
bool wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& relative_time, Predicate pred);
调用 notify_one(), notify_all(), 指定时间内达成条件或线程伪唤醒时,并且谓词为 true 时结束等待.

void notify_one() noexcept; 唤醒一个等待当前 std::condition_variable 实例的线程
void notify_all() noexcept; 唤醒所有等待当前 std::condition_variable 实例的线程

一个线程安全的队列设计:

    #ifndef _THREAD_SAFE_QUEUE_
    #define _THREAD_SAFE_QUEUE_

    #include <condition_variable>
    #include <mutex>
    #include <queue>
    #include <memory>

    template<typename Ty, typename ConditionVar = std::condition_variable, typename Mutex = std::mutex>
    class ThreadSafeQueue
    {
        typedef std::queue<Ty>                Queue;
        typedef std::shared_ptr<Ty>           SharedPtr;
        typedef std::lock_guard<Mutex>        MutexLockGuard;
        typedef std::unique_lock<Mutex>       MutexUniqueLock;

    public:
        explicit ThreadSafeQueue() {}
        ~ThreadSafeQueue() {}

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

        bool IsEmpty() const
        {
            MutexLockGuard lk(mMutex);
            return mQueue.empty();
        }

        void WaitAndPop(Ty& value)
        {
            MutexUniqueLock lk(mMutex);
            mConVar.wait(lk, [this] {
                return !mQueue.empty();
            });
            value = mQueue.front();
            mQueue.pop();
        }

        SharedPtr WaitAndPop()
        {
            MutexUniqueLock lk(mMutex);
            mConVar.wait(lk, [this] {
                return !mQueue.empty();
            });
            SharedPtr sp = std::make_shared<Ty>(mQueue.front());
            mQueue.pop();
            return sp;
        }

        bool TryPop(Ty& value)
        {
            MutexLockGuard lk(mMutex);
            if (!mQueue.empty())
                return false;
            value = mQueue.front();
            mQueue.pop();
            return true;
        }

        SharedPtr TryPop()
        {
            MutexLockGuard lk(mMutex);
            if (!mQueue.empty())
                return false;
            SharedPtr sp = std::make_shared<Ty>(mQueue.front());
            mQueue.pop();
            return sp;
        }

        void Push(const Ty& value)
        {
            MutexLockGuard lk(mMutex);
            mQueue.push(value);
            mConVar.notify_all();
        }

    private:
        mutable Mutex             mMutex;
        ConditionVar              mConVar;
        Queue                     mQueue;
    };

    #endif // _THREAD_SAFE_QUEUE_

std::future

期望(std::future)可以用来等待其他线程上的异步结果, 其实例可以在任意时间引用异步结果.
C++包括两种期望, std::future(唯一期望) 和 std::shared_future(共享期望)
std::future 的实例只能与一个指定事件相关联.
std::shared_future 的实例能关联多个事件, 它们同时变为就绪状态, 并且可以访问与事件相关的任何数据.
在与数据无关的地方,可以使用 std::future<void> 与 std::shared_future<void> 的特化模板.
期望对象本身并不提供同步访问, 如果多个线程要访问一个独立的期望对象, 需要使用互斥体进行保护.

std::packaged_task

std::packaged_task 可包装一个函数或可调用的对象, 并且允许异步获取该可调用对象产生的结果, 返回值通过 get_future 返回的 std::future 对象取得, 其返回的 std::future 的模板类型为 std::packaged_task 模板函数签名中的返回值类型.
std::packaged_task 对象被调用时, 就会调用相应的函数或可调用对象, 将期望置为就绪, 并存储返回值.
std::packaged_task 的模板参数是一个函数签名, 如 int(std::string&, double*), 构造 std::packaged_task 实例时必须传入一个可以匹配的函数或可调用对象, 也可以是隐藏转换能匹配的.

    std::packaged_task<std::string(const std::string&)> task([](std::string str) {
        std::stringstream stm;
        stm << "tid:" << std::this_thread::get_id() << ", str:" << str << std::endl;
        std::cout << stm.str();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return std::string("MSG:Hello");
    });
    std::future<std::string> f = task.get_future();
    std::thread t(std::move(task), std::string("package task test"));
    t.detach();
    // 调用 f.get 返回结果, 但是须阻塞等到任务执行完成
    std::cout << "main tid:" << std::this_thread::get_id() << ", result: " << f.get() << std::endl; 

std::promise

std::promise 类型模板提供设置异步结果的方法, 这样其他线程就可以通过 std::future 实例来索引该结果.

    class SquareRoot
    {
        std::promise<double>& prom;
    public:
        SquareRoot(std::promise<double>& p) : prom(p) {}
        ~SquareRoot() {}
        void operator()(double x)
        {
            if (x < 0)
            {
                prom.set_exception(std::make_exception_ptr(std::out_of_range("x<0")));
            }
            else
            {
                double result = std::sqrt(x);
                prom.set_value(result);
            }
        }
    };

    std::promise<double> prom;
    SquareRoot p(prom);
    std::thread t(std::bind(&SquareRoot::operator(), &p, 1));
    //std::thread t(std::bind(&SquareRoot::operator(), &p, -1));
    std::future<double> f = prom.get_future();
    try
    {
        double v = f.get();
        std::cout << "value:" << v << std::endl;
    }
    catch (std::exception& e)
    {
        std::cout << "exception:" << e.what() << std::endl;
    }
    t.join();
时间: 2024-10-12 17:50:28

C++并发编程 等待与唤醒的相关文章

【Java并发编程实战】—– AQS(三):阻塞、唤醒:LockSupport

在上篇博客([Java并发编程实战]-– AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 lock方法,在调用acquireQueued(): if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; 在acquireQueued()中调用parkAndCheckIn

超强图文|并发编程【等待/通知机制】就是这个feel~

你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough 现陆续将Demo代码和技术文章整理在一起 Github实践精选 ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star 并发编程为什么会有等待通知机制 上一篇文章说明了 Java并发死锁解决思路 , 解决死锁的思路之一就是 破坏请求和保持条件, 所有柜员都要通过唯一的账本管理员一次性拿到所有

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二

[笔记][Java7并发编程实战手册]系列目录 概要 接上一篇文章,练习修改锁的公平性,和在所中使用条件. 修改锁的公平性ReentrantLock /** *构造一个锁对象,默认为非公平锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 根据ReentrantLock的构造可以看出来,默认会构造非公平锁: 公平锁与非公平锁有什么区别 公平锁 :有多个线程并发访

python并发编程之多进程

python并发编程之多进程 一.什么是进程 进程:正在进行的一个过程或者一个任务,执行任务的是CPU. 原理:单核加多道技术 二.进程与程序的区别 进程是指程序的运行过程 需要强调的是:同一个程序执行两次是两个进程,比如打开暴风影音,虽然都是同一个软件,但是一个可以播放苍井空,另一个可以播放武藤兰. 三.并发与并行 无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务. (1)并发

Java并发编程的艺术(六)——线程间的通信

多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(三)--volatile 1.1 如何实现通信? 这两种方式都采用了同步机制实现多条线程间的数据通信.与其说是"通信",倒不如说是"共享变量"来的恰当.当一个共享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作共享内存,从而各个

JAVA并发编程J.U.C学习总结

前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www.cnblogs.com/chenpi/p/5614290.html 本文目录如下,基本上涵盖了J.U.C的主要内容: JSR 166及J.U.C Executor框架(线程池. Callable .Future) AbstractQueuedSynchronizer(AQS框架) Locks & C

Java并发编程总结3——AQS、ReentrantLock、ReentrantReadWriteLock

本文内容主要总结自<Java并发编程的艺术>第5章——Java中的锁. 一.AQS AbstractQueuedSynchronizer(简称AQS),队列同步器,是用来构建锁或者其他同步组建的基础框架.该类主要包括: 1.模式,分为共享和独占. 2.volatile int state,用来表示锁的状态. 3.FIFO双向队列,用来维护等待获取锁的线程. AQS部分代码及说明如下: public abstract class AbstractQueuedSynchronizer extend

Java并发编程学习笔记

Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现Runnable接口并编写run方法,使得该任务可以执行你的命令.   class MyTask implements Runnable {    private String mName;     public MyTask(String name) {    mName = name;   }  

并发编程实践五:ReentrantLock

ReentrantLock是一个可重入的互斥锁,实现了接口Lock,和synchronized相比,它们提供了相同的功能,但ReentrantLock使用更灵活,功能更强大,也更复杂.这篇文章将为你介绍ReentrantLock,以及它的实现机制. ReentrantLock介绍 通常,ReentrantLock按下面的方式使用: public class ReentrantLockTest { private final ReentrantLock lock = new ReentrantLo