设为首页 加入收藏

TOP

《C++并发编程实战》读书笔记(3):并发操作的同步(一)
2023-09-09 10:25:34 】 浏览:147
Tags:程实战

1、条件变量

当线程需要等待特定事件发生、或是某个条件成立时,可以使用条件变量std::condition_variable,它在标准库头文件<condition_variable>内声明。

std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread()
{
    while (more_data_to_prepare())
    {
        const data_chunk data = prepare_data();
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(data);
        data_cond.notify_one();
    }
}
void data_processing_thread()
{
    while (true)
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [] { return !data_queue.empty(); });
        data_chunk data = data_queue.front();
        data_queue.pop();
        lk.unlock();
        process(data);
        if (is_last_chunk(data)) { break; }
    }
}

wait()会先在内部调用lambda函数判断条件是否成立,若条件成立则wait()返回,否则解锁互斥并让当前线程进入等待状态。当其它线程调用notify_one()时,当前调用wait()的线程被唤醒,重新获取互斥锁并查验条件,若条件成立则wait()返回(互斥仍被锁住),否则解锁互斥并继续等待。

wait()函数的第二个参数可以传入lambda函数,也可以传入普通函数或可调用对象,也可以不传。

notify_one()唤醒正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待的线程多于一个,则唤醒的线程是不确定的。notify_all()唤醒正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作。

2、使用future等待一次性事件发生

C++标准程序库有两种future,分别由两个类模板实现,即std::future<>std::shared_future<>,它们的声明位于头文件<future>内。

2.1、从后台任务返回值

由于std::thread没有提供直接回传结果的方法,所以我们使用函数模板std::async()来解决这个问题。std::async()以异步方式启动任务,并返回一个std::future对象,运行函数一旦完成,其返回值就由该对象持有。在std::future对象上调用get()方法时,当前线程就会阻塞,直到std::future准备妥当并返回异步线程的结果。std::future模拟了对异步结果的独占行为,get()仅能被有效调用一次,调用时会对目标值进行移动操作。

int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
    std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    std::cout << "The answer is " << the_answer.get() << std::endl;
}

在调用std::async()时,它可以接收附加参数进而传递给任务函数作为其参数,此方式与std::thread的构造函数相同。更多启动异步线程的方法可参考下面的例程:

struct X
{
    void foo(int, const std::string&);
    std::string bar(const std::string&);
};
X x;
auto f1 = std::async(&X::foo, &x, 42, "hello"); // 调用p->foo(42, "hello"),p是指向x的指针
auto f2 = std::async(&X::bar, x, "goodbye");    // 调用tmpx.bar("goodbye"), tmpx是x的拷贝副本
struct Y
{
    double operator()(double);
};
Y y;
auto f3 = std::async(Y(), 3.141);         // 调用tmpy(3.141),tmpy是由Y()生成的匿名变量
auto f4 = std::async(std::ref(y), 2.718); // 调用y(2.718)
X baz(X&);
std::async(baz, std::ref(x)); // 调用baz(x)

我们还能为std::async()补充一个std::launch类型的参数,来指定采用哪种方式运行:std::launch::deferred指定在当前线程上延后调用任务函数,等到在future上调用了wait()get(),任务函数才会执行;std::launch::async指定必须开启专属的线程,在其上运行任务函数。该参数的还可以是std::launch::deferred | std::launch::async,表示由std::async()的实现自行选择运行方式,这也是这项参数的默认值。

auto f6 = std::async(std::launch::async, Y(), 1.2); // 在新线程上执行
auto f7 = std::async(std::launch::deferred, baz, std::ref(x)); // 在wait()或get()调用时执行
auto f8 = std::async(std::launch::deferred | std::launch::async, baz, std::ref(x)); // 交由实现自行选择执行方式
auto f9 = std::async(baz, std::ref(x));
f7.wait(); // 调用延迟函数

2.2、关联future实例和任务

std::packaged_task<>连结future对象与函数(或可调用对象,下同)。std::packaged_task<>对象在执行任务时,会调用关联的函数,把返回值保存为future的内部数据,并令future准备就绪。若一项庞杂的操作能分解为多个子任务,则可以把它们分别包装到多个std::packaged_task<>实例之中,再传递给任务调度器或线程池,这就隐藏了细节,使任务抽象化,让调度器得以专注处理std::packaged_task<>实例,无需纠缠于形形色色的任务函数。

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇《C++并发编程实战》读书笔记(1).. 下一篇Web服务器部署上线的踩坑流程回顾..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目