并发,即多个任务同时执行,广泛用于提高吞吐率(使用多个处理器完成单个运算)或提高响应能力(当程序的一部分等待响应时允许另一部分继续执行)。
    我们在5.3节中已经介绍了C++标准对并发的支持,当然只是一种导览的形式,本章和下一章将提供更加细致、更加系统化的介绍。
    如果一项活动可能与其他活动并发执行,我们就称之为任务(task)。线程( thread)是执行任务的计算机特性在系统层面的表示。一个标准库 thread(见42.2节)可执行一个任务。一个线程可与其他线程共享地址空间。即,在单一地址空间中的所有线程能访问相同的内存位置。而并发系统程序员所面临的重要挑战之一就是,确保多线程并发访问内存的方式是合理的。
    标准库对并发的支持包括:

    • 内存模型( memory model):这是对内存并发访问的一组保证(见41.2节),主要是确保简单的普通访问能按人们的朴素预期工作;
    • 对无锁编程 programming without locks)的支持:这是一些避免数据竞争的细粒度底层机制(见41.3节);
    • 一个线程( thread)库:这是一组支持传统线程-锁风格的系统级并发编程的组件,如 threadcondition_variablemutex(见42.2节);
    • 一个任务(task)支持库:这是一些支持任务级并发编程的特性:futurepromisepackage_taskasync()(见42.4节)。

    这些主题是按照从最基础、最底层到最高层的顺序排列的。内存模型是所有编程风格所共用的。为提高程序员开发效率、尽量减少错误,应在尽可能高的层次上编程。例如,应优先选择 future而不是 mutex实现信息交换;除非是简单的计数器,否则应优选 mutex而不是atomic;诸如此类。尽量将复杂任务留给标准库实现者。

    在C+标准库的语境中,一个锁(lock)就是一个 mutex(一个互斥量)以及任何构建于 mutex之上的抽象,用来提供对资源的互斥访问或同步多个并发任务的进度。
    进程( process)即运行于独立地址空间、通过进程间通信机制进行交互的线程【 Tanenbaum,2007】,并不在本书介绍范围之内。我猜在学习了共享数据管理的相关问题和技术之后,你可能会对我的观点“最好避免显式数据共享”产生共鸣。自然,通信也意味着某种形式的共享,但大多数情况下应用程序员不必直接管理这种共享。
    还请注意,只要你不向其他线程传递局部数据的指针,你的局部数据就不存在这里讨论的诸多问题。这也是避免使用全局数据的另一个原因。
    本章不是对并发编程的一个全面介绍,甚至不会全面介绍C++标准库并发编程特性。
    本章讨论:

    • 必须处理系统级并发的程序员所面临问题的基本介绍;
    • 标准并发特性的一个相当详细的综述;
    • 介绍线程-锁层次及更高层次上标准库并发特性的基本使用。

    本章不讨论:

    • 放松内存模型或无锁编程的细节;
    • 高级并发编程和设计技术。

    并发编程和并行编程是学术界很受关注的主题,已广泛应用超过40年,因此有大量专门的文献(例如,基于C++的并发编程可参考【 Wilson,1996】)。特别是,几乎所有 POSIX线程的介绍中的例子都可以用本章介绍的标准库特性进行简单改进。
    与C风格POSⅨ特性以及很多旧式C++线程支撑库不同,标准库线程支持是类型安全的。再没有任何理由去乱用宏或void*来实现线程间信息传递了。类似地,我们可以以函数对象(如 lambda)的形式定义任务并将它们传递给线程,而无须进行类型转换或担心类型违规。而且,也没有理由再去发明精致的协议将来自一个线程的错误消息报告给另一个线程— future(见53.5.1节和42.4.4节)已可传递异常。考虑到并发软件通常很复杂,而且运行于不同线程中的代码通常是分别开发的,我认为类型安全和一种标准的(最好是基于异常的)错误处理策略甚至比在单线程软件场景下更为重要。此外,标准库线程支持还大幅度简化了符号表示。