《C++并发编程实战》读书笔记3---线程同步

来源:互联网 发布:淘宝女用催情药 编辑:程序博客网 时间:2024/06/06 12:25

1.什么是线程同步

线程同步就是说希望一个线程等待特定事件的发生或是一个条件变为true。


2.线程同步的种方法

(1)条件变量(condition_variable)

条件变量就是说,用一个专门的条件变量对象data_cond,它是多个thread都可以访问的。

在一个thread中准备好另一个thread等待的条件后,就通知那个等待线程。

等待线程收到通知,就知道条件满足,就可以继续执行了。

(2)future

说到底就是一个异步结果获得的对象,将任务与一个future对象关联,任务做完后就可以通过future对象得到结果,

当我们需要时就可以随时去从future对象取得我们期待的结果。

(3)两者的区别

①本书表述的两者的区别就是,所有的future都是独一无二的,只是执行一次,然后结果会返回给future,如果还来就需要重新设置。

而条件变量一次设置可以一直,循环,可以等待,收到通知,处理,又等待这样不停循环。

所以如果等待线程只打算等待一次,那么当条件为true时它就不会在等待这个条件了,这个时候就可以使用future。

②条件变量的方式是,我执行完成了需要的任务,然后我通知你,你自己来检查看条件满足没有。

而future都是可以返回你需要的一个值。

future在这点上提供了等多的灵活性。


3.条件变量

std::condition_variable cond;

cond.notify_one();

cond.notify_all();

cond.wait(lck, pred);


(1)关于两个notify函数

就是准备条件的线程,进行完了等待线程等待的东西,然后就通知。

字如其名,一个通知随机一个等待线程,一个通知所有等待的线程。

(2)关于wait的细节

首先说两个参数。

lck是进行共享数据操作的那个锁,只有在锁定的时候,wait函数才会去检查条件是否满足。

pred是一个可调用对象,其返回值应该是一个bool,当返回true时,则等待条件满足,可以进行后续操作。通常用lambda表达式很方便。


wait首先就锁定,然后检查条件满足没有(这样就算notify发生在wait调用之前,也没有关系啊。)

如果条件满足,则继续执行。

如果条件不满足,则将lck解锁,释放对共享数据的锁定。等待通知,一旦通知到达,那么再次锁定,然后判定条件满足没有。


注:wait等待条件满足是直接与等待条件相关的,而与通知到达无关,通知到达了,只是唤醒等待线程重新锁定共享数据,检查条件满足没有。

这样就提供了更多的灵活性。


4.future

这里我需要再讲述一下,future的原理。

future对象就是一个异步取得执行结果的对象,而异步需要执行的任务是通过provider来设定的。

通过在定义provider的时候,将future与某个task关联在一起,就能通过future异步取得执行结果(假设fut为future对象,通过ful.get()取得结果)了。

future也是一个模板类future<T>,T表示future返回结果的类型。


而provider有三种:①provider函数std::async()

②provider模板类std::packaged_task<>

③provider模板类std::promise


(1)std::async()

它通过传入参数,表示出需要执行的任务,然后返回一个future对象,这个对象就是和该任务关联的,然后可以在任意时刻通过该future对象取得某个结果。

当然如果任务还在执行,fut.get()就会阻塞一直到返回结果。

下面来说一说参数std::async(A,B,C,D)

A是执行的方式,std::launch::deferred,std::launch::async,。

deferred表示延迟执行,即在future上调用get()或者wait()才开始执行。

async表示立即启动一个新线程执行。

B是需要执行任务的函数名

C表示,调用需要执行任务的函数的对象。

D表示函数的参数,所以D可以有很多项。


其中A,C可以省略,A省略表示由具体实现选择执行方式。C省略是因为可能不是一个类的成员函数,所以可以直接调用。


(2)std::packaged_task<>

它说白了也是字如其名,是将一个可调用对象(即需要执行的任务)和一个future封装在一起的可调用对象。

而其模板参数不是某个类,而是函数签名(包括函数的返回值,以及参数列表。比如int(int, char,double)或者void())

当定义一个std::packaged_task<>对象时,需要在模板里面填入函数的签名,然后用该函数名以及参数对这个对象初始化。

假设task就是一个std::packaged_task<>对象。

那么auto fut = task.get_future();          就返回了与要执行任务绑定的future对象,而这个future对象的<T>就是之前的函数签名的返回值。

因为task是一个可调用对象,所以在对task对象调用的时候,其封装的任务就开始执行。

随时都能通过fut取得,调用的结果。


(3)std::promise<>

还是字如其名,它就是做一个承诺,说我必定会给你传递一个值的。

然后通过,一个std::promise对象返回的future对象,可以随时取得,这个传递的值。

std::promise<T>,这个T是一个类,表示当我完成任务以后,我想要传递给future值的类型。

std::promise<int> prom;

auto fut = prom.get_future;     这里取得future对象的形式和(2)相同,而future对象的<T>与promise相同,这里就是int。

然后我们在某个线程里面准备好了任务以后就可以传递某个信息了,

通过prom.setvalue(10);

然后随时也可以通过future对象来得到。


(4)std::promise<>和std::packaged_task<>的区别

以下就摘抄一些书中原句。

std::packaged_task<>是比std::promise<>更高层次的抽象。

①无法用一个简单的函数调用表达的任务

②那些结果可能来自不止一个地方的任务

可以通过std::promise来显示地设置值。


其实这里对于①,②我都理解滴不太深刻。

0 0