C++并发编程指南01

1.1.1 计算机的并发

定义:计算机的并发指在单个系统中同时执行多个独立任务。

历史背景

  • 多任务操作系统:多年前,计算机通过任务切换实现多任务运行。
  • 多处理器服务器:早期已实现并行计算。

并发流行的原因:真正的并行。

单核处理器

  • 任务切换:通过快速切换任务,制造并发的假象。
  • 局限性:每次只能执行一个任务,切换有开销。

多核处理器

  • 硬件并发:真正并行多个任务。
  • 普及:多核处理器在台式机中越来越常见。

任务切换与硬件并发

  • 任务切换:单核处理器上,任务交替执行,有切换开销。
  • 硬件并发:多核处理器上,任务真正并行执行。

硬件线程

  • 多线程:某些处理器在一个核心上可执行多个线程。
  • 数量重要性:硬件线程数量决定并发任务数。

任务切换的持续重要性

  • 任务数量:即使有硬件并发,任务切换仍适用于任务数超过硬件并行能力的情况。
  • 后台任务:任务切换使后台任务得以运行。

实际应用

  • 图1.1:双核机器上任务并行 vs. 单核机器上任务切换。
  • 图1.2:双核处理器上四个任务的任务切换。

总结

  • 技术适用性:无论单核还是多核,任务切换和硬件并发技术都适用。
  • 硬件影响:并发使用方式取决于可用硬件并发能力。

在这里插入图片描述
图一

在这里插入图片描述
图二

1.1.2 并发的方式

类比

  • 两个程序员在两个办公室:代表多进程并发。
    • 优点:独立工作,互不干扰。
    • 缺点:沟通困难,管理成本高。
  • 两个程序员在一个办公室:代表多线程并发。
    • 优点:沟通方便,资源共享。
    • 缺点:注意力分散,资源竞争。

并发的基本途径

  • 开发人员:代表线程。
  • 办公室:代表进程。

多进程并发

定义:将应用程序分为多个独立的进程同时运行。

特点

  • 进程间通信:通过信号、套接字、文件、管道等,但通常复杂且速度慢。
  • 进程保护:操作系统保护进程,防止数据被修改。
  • 固定开销:启动进程和管理进程需要额外资源。

优点

  • 安全性:操作系统提供保护和高级通信机制,易于编写安全并发代码。
  • 分布式支持:可在不同机器上运行进程,提高并行可用性和性能。

缺点

  • 通信成本高:进程间通信复杂且开销大。
  • 资源消耗:运行多个进程需要更多资源。

应用场景:适用于需要高隔离性和安全性的场景,如分布式系统。


多线程并发

定义:在单进程中运行多个线程。

特点

  • 线程共享地址空间:线程间可访问大部分数据(全局变量、指针等)。
  • 轻量级:线程开销远小于进程。
  • 数据一致性:需确保线程访问的数据一致,增加了线程通信的复杂性。

优点

  • 高效通信:通过共享内存通信,开销小。
  • 资源利用率高:线程共享资源,减少管理成本。

缺点

  • 数据竞争:共享内存可能导致数据不一致。
  • 复杂性:需要额外机制(如锁)确保线程安全。

应用场景:适用于需要高效通信和资源共享的场景,如高性能计算。


总结

  • 多进程并发:适合高隔离性和分布式场景,但通信成本高。
  • 多线程并发:适合高效通信和资源共享,但需处理数据竞争。
  • C++的并发实现:本书专注于多线程并发,因其高效且主流。

图例

  • 图1.3:多进程间通信。
  • 图1.4:多线程间通过共享内存通信。

后续内容:本书将重点讨论多线程并发及其实现。

在这里插入图片描述

图 1.3
在这里插入图片描述
图1.4

1.2 为什么使用并发

原因

  1. 分离关注点(SOC)
  2. 性能提升

1.2.1 分离关注点

定义:将相关代码与无关代码分离,使程序更易理解、测试和维护。

应用场景

  • 示例:DVD播放程序。
    • 功能1:播放DVD(解码数据并输出到硬件)。
    • 功能2:处理用户输入(如“暂停”、“退出”)。
  • 单线程问题:需要定期检查用户输入,导致代码耦合。
  • 多线程解决方案
    • 线程1:处理用户界面。
    • 线程2:播放DVD。
    • 交互:用户点击“暂停”时,线程间通信。

优点

  • 响应性:用户界面线程可立即响应用户请求。
  • 清晰性:任务分离使代码更简单、更易维护。
  • 独立性:线程划分基于设计,不依赖CPU核心数。

1.2.2 性能

背景

  • 多核处理器普及:芯片制造商通过多核设计提升性能。
  • 挑战:开发者需设计并发软件以利用多核性能。

性能提升方式

  1. 任务并行(Task Parallelism)

    • 定义:将单个任务拆分为多个部分并行执行。
    • 类型
      • 过程并行:不同线程执行算法的不同部分。
      • 数据并行:不同线程对不同数据块执行相同操作。
    • 易并行算法:具有良好的可扩展性,随硬件线程增加而提升性能。
  2. 处理更大问题

    • 定义:通过并行处理更多数据,提升吞吐量。
    • 示例:并行处理图片的不同区域,支持更高分辨率视频。

限制

  • 依赖关系:任务间可能存在依赖,增加复杂性。
  • 资源限制:线程数量受系统资源限制。

1.2.3 什么时候不使用并发

原因:收益小于成本。

成本

  1. 复杂性
    • 多线程代码难以理解和维护。
    • 增加的复杂性可能导致更多错误。
  2. 性能开销
    • 线程启动和上下文切换的开销可能超过性能增益。
    • 任务完成时间短时,单线程可能更高效。
  3. 资源限制
    • 线程数量受系统资源(如内存、地址空间)限制。
    • 过多线程可能导致系统性能下降。

适用场景

  • 性能关键部分:只有性能关键部分值得并发化。
  • 设计清晰性:如果分离关注点带来的清晰性优于性能收益,也可使用并发。

总结

  • 权衡:使用并发需权衡性能增益与代码复杂性。
  • 优化策略:并发是优化策略之一,需谨慎使用。

结论

  • C++开发者的多线程意义:多线程是提升性能和分离关注点的重要工具,但需根据具体场景权衡使用。

后续内容:本书将深入探讨如何在C++中实现多线程并发。

并发和多线程

1.3.1 C++多线程历史
  • C++98标准:不支持线程,缺乏内存模型。因此,在没有编译器扩展的情况下无法编写多线程应用。
  • 平台相关扩展:供应商通过POSIX或Windows API等提供多线程支持,但这些依赖于特定平台且需要相应的运行库支持。
  • 第三方库:如MFC、Boost和ACE提供了面向对象的多线程工具,简化了任务管理。
1.3.2 支持并发
  • C++11标准:引入了新的内存模型和扩展的标准库,包括线程管理、数据保护、同步操作及原子操作。
  • 继承自Boost:许多C++11线程库类的设计与Boost线程库相似,方便已有用户过渡。
  • 语言改进:C++11不仅限于并发支持,还包括对语言本身的多项改进,有利于简化并行编程。
1.3.3 C++14和C++17对并发和并行的支持
  • C++14:增加了新的互斥量类型以更好地保护共享数据。
  • C++17:引入了并行算法,进一步增强了标准库的功能,使编写多线程代码更加便捷。
  • 原子操作:直接在语言层面支持,减少了对平台特定汇编指令的依赖,提高了代码的可移植性和效率。
1.3.4 C++线程库的效率
  • 抽象代价:C++标准委员会在设计线程库时注重性能,确保高级API与底层API之间的性能差距最小化。
  • 灵活性:提供了从低级到高级的各种工具,允许开发者根据需求选择合适的工具进行开发。
  • 优化考虑:尽管高级工具可能带来额外开销,但在大多数情况下,这种开销是可控的,并且可以通过良好的程序设计来减少性能瓶颈。
1.3.5 平台相关的工具
  • native_handle():C++线程库中提供的函数,允许访问平台特定的API,以便在标准库不足以满足需求时使用更底层的工具。
  • 慎重选择:虽然可以利用平台特定的工具实现更高的性能或特殊功能,但这通常意味着增加复杂度和维护成本,因此应在充分评估后谨慎采用。

总结来说,随着C++版本的演进,特别是从C++11开始,对并发和多线程的支持有了显著增强,使得开发者能够编写出高效、可移植且易于维护的多线程应用程序。同时,对于追求极致性能的应用场景,C++也提供了足够的灵活性和底层访问能力。

开始入门

1.4.1 欢迎来到并发世界

为了编写C++多线程程序,首先需要一个支持C++11/C++14/C++17标准的编译器。下面通过对比单线程和多线程版本的“Hello World”程序来介绍如何使用C++进行并发编程。

单线程 “Hello World” 程序
#include <iostream>
int main() {
  std::cout << "Hello World\n";
}

这个简单的程序将字符串“Hello World”输出到标准输出流。

多线程 “Hello, Concurrent World” 程序
#include <iostream>
#include <thread>  // 1

void hello() {  // 2
  std::cout << "Hello Concurrent World\n";
}

int main() {
  std::thread t(hello);  // 3
  t.join();  // 4
}

关键点解析:

  1. 包含头文件 <thread>:这是使用C++标准库中多线程功能的第一步,提供了管理线程的类和函数。

  2. 定义独立函数 hello():每个线程都需要一个执行起点,这里定义了一个简单的函数hello()用于新线程的启动。

  3. 创建并启动新线程:通过std::thread t(hello);创建了一个新的线程t,该线程开始执行hello()函数中的代码。

  4. 等待线程完成:调用t.join();让主线程等待t线程执行完毕后再继续执行后续代码。如果不调用join(),主线程可能会在t线程之前结束,导致程序提前退出。

总结:

  • 区别与联系:相较于单线程程序,多线程版本主要增加了线程管理的逻辑,包括引入了<thread>库、定义了独立的执行单元(函数),并通过std::thread对象启动新线程,最后使用join()确保所有线程正确完成。
  • 适用场景:尽管这个例子展示了如何创建和管理线程,但对于如此简单的任务来说,并不推荐使用多线程,因为这样不仅没有性能上的优势,还增加了复杂性。更多复杂的实例将在后续章节中展示,说明何时及如何有效地利用多线程技术。

通过这些基础示例,你可以开始探索更高级的并发编程概念和技术,逐步构建出高效且可维护的多线程应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金_chihiro_修行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值