《C++并发编程实战》读书笔记(2):并发操作的同步

C语言与CPP编程 2023-06-11 09:00

击上方“C语言与CPP编程”,选择“关注/置顶/星标公众号

干货福利,第一时间送达!

你好,我是飞宇。

昨天在朋友圈分享了一下自己关于《C++并发编程实战》这本书的读书笔记,收到不少点赞,今天就在公众号上分享一下自己以前的读书笔记,今天继续更新。

前段时间也更新了第一部分:《C++并发编程实战》读书笔记(1):并发、线程管控

这里也顺便放一下自己的个人联系方式的二维码,听说以后公众号后不可以放二维码了我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推工作机会,一般不闲聊,欢迎来做点赞之交。

C++国内并发编程的书少得可怜,唯一本质量还稍微靠谱点的就这本了《C++并发编程实战》,算是矮子里了挑将军挑出来的吧。

第4章 并发操作的同步

4.1 等待事件或等待其他条件

    如果线程甲需要等待线程乙完成任务,可以使用C++标准库的条件变量来等待事件发生。中提供了condition_variable和condition_variable_any,前者只能配合mutex使用,而后者可以与任意符合互斥标准的类型使用,会产生额外开销。主要使用成员函数wait、notify_one、notify_all。

    例如可以实现一个生产者消费者模型,通过队列来传递数据,一端准备数据另一端处理数据,其中条件变量的作用是消费者线程取出数据前检查队列是否非空,否则释放锁并等待生产者线程准备数据。


std::mutex mut;std::queue data_queue;std::condition_variable data_cond;
void data_preparation_thread() { while (...) {        const Widget data = prepare_data();        { std::lock_guard<std::mutex> lk(mut); data_queue.push(data);        } // 通知消费者线程 data_cond.notify_one(); }}
void data_processing_thread() {    while (...) {        // 需要多次加锁解锁,所以用unique_lock std::unique_lock<std::mutex> lk(mut); // wait首先判断lambda,成立则返回,否则解锁互斥进入阻塞        // 每次被notify后解除阻塞并获取锁,重复上述过程 data_cond.wait(lk, [] { return !data_queue.empty(); });        Widget data = data_queue.front(); data_queue.pop(); lk.unlock();        process(data); }}


    也可以实现一个简略的线程安全的队列:

template <typename T>class threadsafe_queue {   private:    mutable std::mutex mut;    std::queue data_queue;    std::condition_variable data_cond;
public: threadsafe_queue() {} threadsafe_queue(threadsafe_queue const& other) { std::lock_guard<std::mutex> lk(other.mut); data_queue = other.data_queue; }
void push(T new_value) { std::lock_guard<std::mutex> lk(mut); data_queue.push(new_value); data_cond.notify_one(); }
void wait_and_pop(T& value) { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this] { return !data_queue.empty(); }); value = data_queue.front(); data_queue.pop();    }};



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

    若线程需等待某一次性事件,可以以适当方式取得一个代表目标事件的future,此后线程就可以一边执行其他任务一边在future上等待。一旦目标事件发生,future就进入就绪状态,无法重置。

    中提供了两种类模板future和shared_future,同一事件仅可关联一个future实例,但可关联多个shared_future,并且目标事件发生后关联的所有shared_future实例都就绪。future本身不提供同步,多线程时需要用同步方式进行保护。


4.2.1 从后台任务返回值

    并不急需某任务的返回值时,可以用async异步地启动任务,获得一个future对象;对后者调用get会阻塞当前线程,等待future准备完并返回该值。

int f() { ... }
std::future<int> answer = std::async(f);...std::cout << answer.get();


    给async的任务函数传递参数类似给thread传递参数。


// 调用成员函数的情况// p=&x; p->foo("hello")auto f1 = std::async(&X::foo, &x, "hello");// tmpx=x; tmpx.foo("hello")auto f2 = std::async(&X::foo, x, "hello");
// 调用仿函数的情况// tmpy=y; tmpy(3.14)auto f3 = std::async(Y(), 3.14);// y(3.14)auto f4 = std::async(std::ref(y), 3.14);
// 函数形参为引用的情况X baz(X&);auto f5 = std::async(baz, std::ref(x));


    可以给async传递参数指定运行方式,deferred代表直到在future上调用wait/get才执行任务函数,async代表开启专属线程来执行;默认为deferred|async。

auto f = std::async(std::launch::async, Y(), 1.2);



4.2.2 关联future实例与任务

    类模板packaged_task把任务包装起来,可作为任务调度器、线程池的构建单元,其模板参数是函数签名,例如int(int,double*)。具备函数调用操作符,参数取决于上述模板参数用时将参数传递给任务函数,通过get_future获取future对象,异步运行得到结果后保存到该对象

    例如图形用户界面需要接收其他线程的消息来更新界面。

std::mutex m;std::deque<std::packaged_task<void()> > tasks;
// 图形用户界面的线程函数void gui_thread() { while (...) { get_and_process_gui_message(); std::packaged_task<void()> task; { std::lock_guard<std::mutex> lk(m); if (tasks.empty()) continue; task = std::move(tasks.front()); tasks.pop_front(); } task(); }}
// 其他线程通过该函数传递消息template <typename Func>std::future<void> post_task_for_gui_thread(Func f) { std::packaged_task<void()> task(f); std::future<void> res = task.get_future(); std::lock_guard<std::mutex> lk(m); tasks.push_back(std::move(task)); return res;}



4.2.3 创建std::promise

    有些任务无法以简单的函数调用表达,或者执行结果来自多个部分的代码,那么就需要使用std::promise显式地异步求值。

    promise通过get_future获取关联的future对象等待数据的线程在future上阻塞,提供数据的线程通过set_value设置数据,设置完后future即就绪promise销毁时仍未set_value,则传递异常。

    下面是单线程处理多个连接的例子。这里假设传入的数据包含有ID与荷载数据,接收后将ID与promise对应,将相关值设为荷载数据。对于传出的数据而言,promise的相关值是代表是否成功的bool。

void process_connections(connection_set& connections) {    while (...) {        for (connection_iterator connection = ...) {            if (connection->has_incoming_data()) {                data_packet data = connection->incoming();                std::promise& p = connection->get_promise(data.id);                p.set_value(data.payload);            }            if (connection->has_outgoing_data()) {                outgoing_packet data = connection->top_of_outgoing_queue();                connection->send(data.payload);                data.promise.set_value(true);            }        }    }}


    async与packaged_task运行的函数抛出异常时会保存在future对象中,调用get时再次抛出。对于promise而言,应用set_exception保存异常

some_promise.set_exception(std::make_exception_ptr(std::logic_error("foo")));



4.2.4 多个线程一起等待

    shared_future可以让多个线程等待同一个目标事件。每个线程复制一份shared_future副本,成为各线程独有的局部变量;通过该局部变量访问将由标准库自动同步,可以安全地访问。

std::promise<int> p1;auto f1 = p1.get_future();assert(f1.valid());std::shared_future<int> sf1 = std::move(f1);assert(!f1.valid());assert(sf1.valid());
std::promise<int> p2;auto sf2 = p2.get_future().share();



4.3 限时等待

    之前介绍的所有可能阻塞的调用,其阻塞都可能漫无止境。为此可以采用一些超时机制:延迟超时表示等待一定时间,后缀为for,绝对超时表示等待到某时间点,后缀为until。

    std::chrono库中时钟是时间信息的来源,每个时钟类都提供当前时刻now、时间值的类型time_point、计时单元的长度ratio<>、计时速率是否恒定is_steady。常用时钟类包括system_clock,steady_clock,high_resolution_clock。

    时长类duration<>,其模板参数有两个,第一个指采用何种类型表示计时单元的数量,第二个指每个计时单元代表多少秒。例如std::chrono::duration>代表采用double值计数的毫秒时长类。

auto f = std::async(some_task);if(f.wait_for(std::chrono::milliseconds(35))==    std::future_status::ready){        process(f.get());}


    时间点类time_point<>,模板参数有两个,第一个指参考时钟,第二个指计时单元,即特化的duration。


std::condition_variable cv;bool done;std::mutex m;
bool wait_loop() { auto const timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(500); std::unique_lock<std::mutex> lk(m); while (!done) { if (cv.wait_until(lk, timeout) == std::cv_status::timeout) break; } return done;}



4.4 运用同步操作简化代码

    在并发实战中可以使用贴近函数式编程的风格,函数调用的结果完全取决于参数而非任何外部状态。线程间不会直接共享数据,而是由各任务分别预先准备妥自己所需的数据,随后通过future将结果发送到其他有需要的线程。

    例如可以实现并行的快排:

template <typename T>std::list parallel_quick_sort(std::list input) {    if (input.empty()) {        return input;    }    // 将input的开头剪切到result    // 以此为分界值,将input分为两段    std::list result;    result.splice(result.begin(), input, input.begin());    T const& pivot = *result.begin();    auto divide_point = std::partition(input.begin(), input.end(),                                       [&](T const& t) { return t < pivot; });    // 异步处理较小的一段    std::list lower_part;    lower_part.splice(lower_part.end(), input, input.begin(), divide_point);    std::future<std::list > new_lower(std::async(¶llel_quick_sortstd::move(lower_part)));    // 本线程处理较大的一段    auto new_higher(parallel_quick_sort(std::move(input)));    // 汇合所有结果    result.splice(result.end(), new_higher);    result.splice(result.begin(), new_lower.get());    return result;}


    除了函数式编程,CSP(通信式串行线程)也有同样特性,其中线程完全隔离,没有共享数据,通过管道传递消息。具体代码这里不再演示。

    C++20中还提出两个新特性:latch和barrier。latch是一个同步对象,内含计数器,减到0时就绪。


void foo() {    unsigned const thread_count = ...;    latch done(thread_count);    my_data data[thread_count];    std::vector<std::future<void> > threads;    for (unsigned i = 0; i < thread_count; ++i)        threads.push_back(std::async(std::launch::async, [&, i] {            data[i] = make_data(i);            done.count_down();            ...        }));    done.wait();    process_data(data, thread_count);}


    而barrier针对一组给定的线程,每个线程运行到barrier处就阻塞,直到同组的所有线程都抵达释放。

void process_data(data_source &source, data_sink &sink) {    unsigned const num_threads = ...    barrier sync(num_threads);    std::vector threads(num_threads);        std::vector chunks;    result_block result;
for (unsigned i = 0; i < num_threads; ++i) { threads[i] = joining_thread([&, i] {            while (...) { if (!i) { data_block current_block = source.get_next_data_block(); chunks = divide_into_chunks(current_block, num_threads); } sync.arrive_and_wait(); result.set_chunk(i, num_threads, process(chunks[i])); sync.arrive_and_wait(); if (!i) { sink.write_data(std::move(result)); } } }); }}
EOF

你好,我是飞宇,本硕均于某中流985 CS就读,先后于百度搜索以及字节跳动电商等部门担任Linux C/C++后端研发工程师。

同时,我也是知乎博主@韩飞宇,日常分享C/C++、计算机学习经验、工作体会,欢迎点击此处查看我以前的学习笔记&经验&分享的资源。

我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。

欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会

加个微信,打开另一扇窗

C语言与CPP编程 C语言/C++开发,C语言/C++基础知识,C语言/C++学习路线,C语言/C++进阶,数据结构;算法;python;计算机基础等
评论
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 71浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 88浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 83浏览
  • 应用环境与极具挑战性的测试需求在服务器制造领域里,系统整合测试(System Integration Test;SIT)是确保产品质量和性能的关键步骤。随着服务器系统的复杂性不断提升,包括:多种硬件组件、操作系统、虚拟化平台以及各种应用程序和服务的整合,服务器制造商面临着更有挑战性的测试需求。这些挑战主要体现在以下五个方面:1. 硬件和软件的高度整合:现代服务器通常包括多个处理器、内存模块、储存设备和网络接口。这些硬件组件必须与操作系统及应用软件无缝整合。SIT测试可以帮助制造商确保这些不同组件
    百佳泰测试实验室 2024-12-12 17:45 74浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 115浏览
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 87浏览
  • 本文介绍瑞芯微RK3588主板/开发板Android12系统下,APK签名文件生成方法。触觉智能EVB3588开发板演示,搭载了瑞芯微RK3588芯片,该开发板是核心板加底板设计,音视频接口、通信接口等各类接口一应俱全,可帮助企业提高产品开发效率,缩短上市时间,降低成本和设计风险。工具准备下载Keytool-ImportKeyPair工具在源码:build/target/product/security/系统初始签名文件目录中,将以下三个文件拷贝出来:platform.pem;platform.
    Industio_触觉智能 2024-12-12 10:27 81浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-12 10:13 50浏览
  • 近日,搭载紫光展锐W517芯片平台的INMO GO2由影目科技正式推出。作为全球首款专为商务场景设计的智能翻译眼镜,INMO GO2 以“快、准、稳”三大核心优势,突破传统翻译产品局限,为全球商务人士带来高效、自然、稳定的跨语言交流体验。 INMO GO2内置的W517芯片,是紫光展锐4G旗舰级智能穿戴平台,采用四核处理器,具有高性能、低功耗的优势,内置超微高集成技术,采用先进工艺,计算能力相比同档位竞品提升4倍,强大的性能提供更加多样化的应用场景。【视频见P盘链接】 依托“
    紫光展锐 2024-12-11 11:50 78浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 78浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 115浏览
  • 全球知名半导体制造商ROHM Co., Ltd.(以下简称“罗姆”)宣布与Taiwan Semiconductor Manufacturing Company Limited(以下简称“台积公司”)就车载氮化镓功率器件的开发和量产事宜建立战略合作伙伴关系。通过该合作关系,双方将致力于将罗姆的氮化镓器件开发技术与台积公司业界先进的GaN-on-Silicon工艺技术优势结合起来,满足市场对高耐压和高频特性优异的功率元器件日益增长的需求。氮化镓功率器件目前主要被用于AC适配器和服务器电源等消费电子和
    电子资讯报 2024-12-10 17:09 99浏览
  • 全球智能电视时代来临这年头若是消费者想随意地从各个通路中选购电视时,不难发现目前市场上的产品都已是具有智能联网功能的智能电视了,可以宣告智能电视的普及时代已到临!Google从2021年开始大力推广Google TV(即原Android TV的升级版),其他各大品牌商也都跟进推出搭载Google TV操作系统的机种,除了Google TV外,LG、Samsung、Panasonic等大厂牌也开发出自家的智能电视平台,可以看出各家业者都一致地看好这块大饼。智能电视的Wi-Fi连线怎么消失了?智能电
    百佳泰测试实验室 2024-12-12 17:33 66浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 88浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦