一、何为并发

刚开始接触计算机编程语言时,我们编写一个程序,在main入口函数中调用其它的函数,计算机按我们设定的调用逻辑来执行指令获得结果。如果我们想在程序中完成多个任务,可以将每个任务实现为一个函数然后根据业务逻辑逐个调用。但如果我们想让多个任务几乎同时执行(时间间隔很小,我们感觉是同时执行的一样),比如一边放歌一边显示歌词,恐怕实现起来就会有明显的顿挫感(比如先播放一句歌声,然后显示一行歌词),影响交互体验。

随着我们对计算性能的要求越来越高,多核心处理器很快普及流行。如果我们想让自己开发的程序更高效的运行,自然要充分发挥多核心处理器的优势。在多核心处理器上同时运行多个任务,比在单核心处理器上顺序执行多个任务高效的多。像单片机这种单核心处理器,在任务较多或者多个任务需要几乎同时执行时,也需要应用多任务并发编程提高对包括处理器在内的各硬件资源的利用效率。

1.1 并发与并行

说了这么多,那什么是并发呢?简单来说,并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。

与并发相近的另一个概念是并行。它们两者存在很大的差别,图示如下:

1.2 硬件并发与任务切换

既然并发是在同一时间段内交替发生即可,不要求同时发生,像单片机上的单核处理器也是可以支持并发多任务处理的,所以有单片机上跑的RTOS(Real-time operating system)诞生。单核心处理器上的多任务并发是靠任务切换实现的,跟多核处理器上的并行多任务处理还是有较大区别的,但对处理器的使用和多任务调度工作主要由操作系统完成了,所以我们在两者之间编写应用程序区别倒是不大。下面再贴个直观的图示:

1.3 多线程并发与多进程并发

前面一直在聊多任务并发,但计算机术语中用得更多的是线程与进程,三者的主要区别如下:

由上面的定义可以看出,一个进程和一个线程最显著的区别是:线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样。

在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。在Linux系统中,线程的实现和进程并不特别区分,线程只不过是一种特殊的进程。多进程并发编程与多线程并发编程的区别主要在有没有共享数据,多进程间的通信较复杂且代价较大,主要的进程间通信渠道有管道、信号、文件、套接字等。由于C++没有提供进程间通信的原生支持,后续主要介绍多线程并发编程,和多线程间的同步与通信。

二、如何使用并发

2.1 为什么使用并发

在应用程序中使用并发的原因主要有两个:关注点分离和性能。事实上,我甚至可以说它们差不多是使用并发的唯一原因;当你观察的足够仔细时,一切其他因素都可以归结到这两者之一(或者可能是二者兼有)。

知道何时不使用并发与知道何时使用它一样重要。基本上,不使用并发的唯一原因就是在收益比不上成本的时候。使用并发的代码在很多情况下难以理解,因此编写和维护的多线程代码就有直接的脑力成本,同时额外的复杂性也可能导致更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消确保其正确所需的额外的开发时间以及与维护多线程代码相关的额外成本,否则不要使用并发。

2.2 在C++中使用并发和多线程

在早期的C++标准中,比如1998 C++标准版不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有被正式定义,所以对于1998 C++标准,你没办法在缺少编译器相关扩展的情况下编写多线程应用程序。如果在之前想使用多线程并发编程,可以借助编译器厂商提供的平台相关的扩展多线程支持API(比如POSIX C和Microsoft Windows API),但这种多线程支持对平台依赖度较高,导致可移植性较差。

为了解决平台相关多线程API使用上的问题,逐渐开发出了Boost、ACE等平台无关的多线程支持类库。直到C++11标准的发布,借鉴了很多Boost类库的经验,将多线程支持纳入C++标准库。C++11标准不仅提供了一个全新的线程感知内存模型,也包含了用于管理线程、保护共享数据、线程间同步操作以及低级原子操作的各个类。

对于C++整体以及包含低级工具的C++类——特别是在新版C++线程库里的那些,参与高性能计算的开发者常常关注的一点就是效率。如果你正寻求极致的性能,那么理解与直接使用底层的低级工具相比,使用高级工具所带来的实现成本,是很重要的。这个成本就是抽象惩罚(abstraction penalty)。标准C++线程库在设计时,就非常注重高效的性能,提供了足够的低级工具(比如原子操作库),以付出尽可能低的抽象惩罚。C++标准库也提供了更高级别的抽象和工具,它们使得编写多线程代码更简单和不易出错。有时候运用这些工具确实会带来性能成本,因为必须执行额外的代码。但是这种性能成本并不一定意味着更高的抽象惩罚;总体来看,这种性能成本并不比通过手工编写等效的函数而招致的成本更高,同时编译器可能会很好地内联大部分额外的代码。

三、C++线程创建

一个多线程C++程序是什么样子的?它看上去和其他所有C++程序一样,通常是变量、类以及函数的组合。唯一真正的区别在于某些函数可以并发运行,所以你需要确保共享数据的并发访问是安全的。当然,为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。

3.1 C++11新标准多线程支持库

3.2 线程创建的简单示例

线程创建和管理的函数或类主要由< thread >库文件来提供,该库文件的主要操作如下:

由上表可知,通过std::thread t(f, args…)创建线程,可以给线程函数传递参数。通过join()函数关联并阻塞线程,等待该线程执行完毕后继续;通过detach()函数解除关联使线程可以与主线程并发执行,但若主线程执行完毕退出后,detach()接触关联的线程即便没有执行完毕,也将自动退出,有时可能这并非我们预期的结果,所以需要特别注意。下面给出一段线程管理的示例代码:

//thread1.cpp  创建线程,并观察线程的并发执行与阻塞等待

#include <iostream>
#include <thread>
#include <chrono>

using namespace std;

void thread_function(int n)
{
std::thread::id this_id = std::this_thread::get_id(); //获取线程ID

<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Child function thread "</span> <span class="token operator">&lt;&lt;</span> this_id<span class="token operator">&lt;&lt;</span> <span class="token string">" running : "</span> <span class="token operator">&lt;&lt;</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token comment">//进程睡眠n秒</span>
<span class="token punctuation">}</span>

}

class Thread_functor
{
public:
// functor行为类似函数,C++中的仿函数是通过在类中重载()运算符实现,使你可以像使用函数一样来创建类的对象
void operator()(int n)
{
std::thread::id this_id = std::this_thread::get_id();

    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Child functor thread "</span> <span class="token operator">&lt;&lt;</span> this_id <span class="token operator">&lt;&lt;</span> <span class="token string">" running: "</span> <span class="token operator">&lt;&lt;</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">//进程睡眠n秒</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>

};

int main()
{
thread mythread1(thread_function, 1); // 传递初始函数作为线程的参数
if(mythread1.joinable()) //判断是否可以成功使用join()或者detach(),返回true则可以,返回false则不可以
mythread1.join(); // 使用join()函数阻塞主线程直至子线程执行完毕

Thread_functor thread_functor<span class="token punctuation">;</span>          <span class="token comment">//函数对象实例化一个对象</span>
thread <span class="token function">mythread2</span><span class="token punctuation">(</span>thread_functor<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token comment">// 传递初始函数作为线程的参数</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>mythread2<span class="token punctuation">.</span><span class="token function">joinable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>mythread2<span class="token punctuation">.</span><span class="token function">detach</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                  <span class="token comment">// 使用detach()函数让子线程和主线程并行运行,主线程也不再等待子线程</span><span class="token keyword">auto</span> thread_lambda <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment">//lambda表达式格式:[capture list] (params list) mutable exception-&gt; return type { function body }</span>std<span class="token operator">::</span>thread<span class="token operator">::</span>id this_id <span class="token operator">=</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">get_id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Child lambda thread "</span> <span class="token operator">&lt;&lt;</span> this_id <span class="token operator">&lt;&lt;</span> <span class="token string">" running: "</span> <span class="token operator">&lt;&lt;</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">//进程睡眠n秒</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>thread <span class="token function">mythread3</span><span class="token punctuation">(</span>thread_lambda<span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token comment">// 传递初始函数作为线程的参数</span>
<span class="token keyword">if</span><span class="token punctuation">(</span>mythread3<span class="token punctuation">.</span><span class="token function">joinable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>mythread3<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                     <span class="token comment">// 使用join()函数阻塞主线程直至子线程执行完毕</span>std<span class="token operator">::</span>thread<span class="token operator">::</span>id this_id <span class="token operator">=</span> std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">get_id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>cout <span class="token operator">&lt;&lt;</span> <span class="token string">"Main thread "</span> <span class="token operator">&lt;&lt;</span> this_id <span class="token operator">&lt;&lt;</span> <span class="token string">" running: "</span> <span class="token operator">&lt;&lt;</span> i<span class="token operator">+</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> endl<span class="token punctuation">;</span>std<span class="token operator">::</span>this_thread<span class="token operator">::</span><span class="token function">sleep_for</span><span class="token punctuation">(</span>std<span class="token operator">::</span>chrono<span class="token operator">::</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token function">getchar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>

}

使用GCC编译为可执行程序的命令如下:

g++ -Wall -g -std=c++11 -pthread thread1.cpp -o thread1
# -Wall显示所有警告,-g输出调试信息,-std=c++11使用c++11标准编译,-pthread编译使用POSIX thread库文件

线程创建的参数是函数对象,函数对象不止是函数指针或成员函数指针,同时还包括函数对象(仿函数)与lambda表达式。上面的代码分别用三种函数对象创建了三个线程,其中第一个线程mythread1阻塞等待其执行完后继续往下执行,第二个线程mythread2不阻塞等待在后台与后面的第三个线程mythread3并发执行,第三个线程继续阻塞等待其完成后再继续往下执行主线程任务。

为了便于观察并发过程,对三个线程均用了睡眠延时this_thread::sleep_for(duration)函数,且延时时间作为参数传递给该函数。这里的参数是支持C++泛型模板的,STL标准容器类型(比如Array/Vector/Deque/List/Set/Map/String等)都可以作为参数传递,但这里的参数默认是以拷贝的方式传递参数的,当期望传入一个引用时,要使用std::ref进行转换。

针对任何线程(包括主线程),< thread > 还声明了一个命名空间std::this_thread,用以提高线程专属的全局函数。函数声明和效果见下表:

上面的代码就是利用了std::this_thread提供的函数获得当前线程的ID,让当前线程睡眠一段时间(一般需要< chrono >头文件提供duration或timepoint)的功能,代码执行结果如下图所示:

上面的示例假如多重复运行几次,有很大可能会出现某行与其他行交叠错乱的情况(如下图所示),为何会出现这种情况呢?这就涉及到多线程资源竞争的问题了,即一个线程对某一资源(这里指显示终端)的访问还未完成,另一线程抢夺并访问了该资源,导致该资源数据混乱情况的出现。解决方案详见下一篇文章:C++多线程并发(二)—线程同步

C++多线程并发中线程管理相关推荐

  1. Java-多线程-Future、FutureTask、CompletionService、CompletableFuture解决多线程并发中归集问题的效率对比

    转载声明 本文大量内容系转载自以下文章,有删改,并参考其他文档资料加入了一些内容: [小家Java]Future.FutureTask.CompletionService.CompletableFut ...

  2. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

  3. 当析构函数遇到多线程 ── C++ 中线程安全的对象回调

    陈硕 (giantchen_AT_gmail) 本文 PDF  下载: http://www.cppblog.com/Files/Solstice/dtor_meets_mt.pdf 摘要 编写线程安 ...

  4. 并发并行多线程并发问题线程安全问题

    1.并行(多个线程). 2.并发(一个线程也可以,指的是指的是 一个线程或多个线程上,多个程序之间的多路复用,即看起来是同时) redis就是这种技术,单线程+多路IO复用 3.我们通常说的并发,就是 ...

  5. java list 多线程add_Java多线程并发中支持并发的list对象

    Java多线程并发编程中并发容器第二篇之List的并发类讲解 概述 本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList.vector以及CopyOnWriteArrayList ...

  6. java多线程并发及线程池

    线程的常用创建方式 1.继承Thread类创建线程类 public class FirstThreadTest extends Thread {public void run(){System.out ...

  7. 多线程并发中什么是竞争条件?

    跟着作者的65节课彻底搞懂Java并发原理专栏,一步步彻底搞懂Java并发原理. 作者简介:笔名seaboat,擅长工程算法.人工智能算法.自然语言处理.计算机视觉.架构.分布式.高并发.大数据和搜索 ...

  8. 多线程并发或线程安全问题如何解决?

    1:通过volatile 关键字修饰变量,可以实现线程之间的可见性, 避免变量脏读的出现,底层是通过限制jvm指令的重排序来实现的 适用于一个线程修改,多个线程读的场景 2:通过synchronize ...

  9. 【zz】陈硕:当析构函数遇到多线程──C++ 中线程安全的对象回调

    需要解决的问题: 析构对象时,如何可知另外的线程正在执行对象的成员的成员函数? 如果保证,执行成员函数期间,对象不会再另外的线程被析构 调用某个对象的成员函数之前,如何得知对象或者? 对象创建:构造时 ...

最新文章

  1. windows下opencv安装及配置(vs2010环境)
  2. java基础进阶一:String源码和String常量池
  3. 三年经验前端社招——腾讯微保
  4. QML的import目录爬坑记录
  5. mysql8 修改加密方式_mysql8修改密码加密方式
  6. 使用OTA绕过AppStore安装App
  7. java中对象 引用的概念_java中的对象 方法 引用 等一些抽象的概念是什么意思呢?...
  8. RTT 操作片上flash
  9. 《深入学习VMware vSphere 6》——1.5 主流服务器的RAID配置
  10. 在 Windows 下远程桌面连接 Linux - XManager 篇
  11. (转)AIX rootvg 镜像创建与磁盘更换
  12. 《非常网管:网络管理从入门到精通(修订版)》一1.4 TCP/IP
  13. 树分类、线性回归和树回归的感性认知
  14. 终极算法 机器学习和人工智能如何重塑世界
  15. 开网店,网店系统的编程语言分析
  16. Win11资源管理器(文件夹)出现的工具栏怎么隐藏?
  17. 谢慧敏清晰版. 数学分析习题课讲义.下. 2004
  18. php开放平台,顺丰开放平台API PHP SDK demo
  19. java事务 spring事务 分布式事物
  20. 1162:字符串逆序

热门文章

  1. 不同路径Python解法
  2. python中输出菱形_用python打印菱形的实操方法和代码
  3. python中的垃圾回收机制_python里面的垃圾回收机制
  4. qemu 安装windows_BIOS+MBR启动引导安装双系统
  5. 系统启动数据库服务器,linux系统如何启动数据库服务器
  6. linux在主函数中调用进程,linux 调用进程
  7. 自己动手写CPU(7)转移指令的实现
  8. 自己动手写CPU(5)简单算术操作指令实现_1
  9. Java表示0到200的质因数_java记——循环 求一个数的所有质因数
  10. python散点图如何设置外边框_如何绘制散点图的外围边框?