5分钟速成C++14多线程编程
原文链接:Learn C++ Multi-Threading in 5 Minutes
C++14的新的多线程架构非常简单易学,如果你对C或者C++很熟悉,那么本文非常适合你。作者用C++14作为基准参考,但是所介绍的东西在C++17中也依然适用。本文只介绍基本的架构,在读完本文后你应该也可以自己编写自己的多线程程序。
创建线程
创建线程有以下几种方式:
1.使用函数指针
2.使用仿函数
3.使用lambda表达式
这些方式都比较类似,只有部分差别,我将在下面具体讲述每一种方式和他们的区别。
使用函数指针
来看看下面这个函数,其参数包括一个vector的引用 v ,一个输出结果的引用acm,还有两个v的索引。这个函数会将v的beginIndex和endIndex之间的元素累加起来。
- void accumulator_function2(const std::vector<int> &v, unsigned long long &acm,
- unsigned int beginIndex, unsigned int endIndex)
- {
- acm = ;
- for (unsigned int i = beginIndex; i < endIndex; ++i)
- {
- acm += v[i];
- }
- }
现在如果我们想将vector分为两个部分,并在单独的线程t1和t2中分别计算各部分的总和的话,我们可以这么写:
- //Pointer to function
- {
- unsigned long long acm1 = ;
- unsigned long long acm2 = ;
- std::thread t1(accumulator_function2, std::ref(v),
- std::ref(acm1), , v.size() / );
- std::thread t2(accumulator_function2, std::ref(v),
- std::ref(acm2), v.size() / , v.size());
- t1.join();
- t2.join();
- std::cout << "acm1: " << acm1 << endl;
- std::cout << "acm2: " << acm2 << endl;
- std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
- }
上面这段你需要知道:
1.std::thread这个调用创建了一个新的线程。其第一个参数是函数指针 accumulator_function2 ,因此每个线程都会去执行这个函数。
2.剩下的我们传给std::thread的构造函数的参数,都是我们需要去传给accumulator_function2的参数。
3.重点:传递给accumulator_function2的参数默认情况下都是值传递的,除非你用std::ref把他包起来。所以我们这里使用了std::ref来包住v、acm1、acm2。
4.使用std::thread创建的线程是没有返回值的,所以如果你想从线程中返回些什么,请使用引用将你想返回的值作为一个传入参数。这里的例子就是acm1和acm2。
5.每个线程一旦创建就立即执行了。
6.我们使用join()函数来等待线程执行完毕。
使用伪函数
你也可以使用伪函数来做同样的事情,下面是例子:
- class CAccumulatorFunctor3
- {
- public:
- void operator()(const std::vector<int> &v,
- unsigned int beginIndex, unsigned int endIndex)
- {
- _acm = ;
- for (unsigned int i = beginIndex; i < endIndex; ++i)
- {
- _acm += v[i];
- }
- }
- unsigned long long _acm;
- };
那么创建线程的方式变成下面这样:
- //Creating Thread using Functor
- {
- CAccumulatorFunctor3 accumulator1 = CAccumulatorFunctor3();
- CAccumulatorFunctor3 accumulator2 = CAccumulatorFunctor3();
- std::thread t1(std::ref(accumulator1),
- std::ref(v), , v.size() / );
- std::thread t2(std::ref(accumulator2),
- std::ref(v), v.size() / , v.size());
- t1.join();
- t2.join();
- std::cout << "acm1: " << accumulator1._acm << endl;
- std::cout << "acm2: " << accumulator2._acm << endl;
- std::cout << "accumulator1._acm + accumulator2._acm : " <<
- accumulator1._acm + accumulator2._acm << endl;
- }
上面这段你需要知道:
伪函数的使用方式大部分地方都和函数指针很像,除了:
1.第一个参数变成了伪函数对象。
2.我们不再需要使用引用来获取返回值了,我们可以将返回值作为伪函数对象的一个成员变量来储存。这里的例子就是_acm。
使用lambda表达式
作为第三种选择,我们可以在每个线程的构造函数中使用lambda表达式来定义我们想做的事,如下:
- {
- unsigned long long acm1 = ;
- unsigned long long acm2 = ;
- std::thread t1([&acm1, &v] {
- for (unsigned int i = ; i < v.size() / ; ++i)
- {
- acm1 += v[i];
- }
- });
- std::thread t2([&acm2, &v] {
- for (unsigned int i = v.size() / ; i < v.size(); ++i)
- {
- acm2 += v[i];
- }
- });
- t1.join();
- t2.join();
- std::cout << "acm1: " << acm1 << endl;
- std::cout << "acm2: " << acm2 << endl;
- std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
- }
同样,大多数地方都和函数指针的方式很类似,除了:
1.作为传参的替代方式,我们可以使用lambda表达式的捕获(capture)方式来处理参数传递
Tasks, Futures, and Promises
除了std::thread,我们还可以使用 tasks.
tasks和std::thread工作的方式非常相似,只有一个最主要的不同:tasks可以返回一个值。因此,你可以暂存这个返回值来作为这个线程的更抽象的定义方式,并在你真的需要返回的结果的时候来从这个返回值中拿到数据。
下面就是使用Tasks的例子:
- #include <future>
- //Tasks, Future, and Promises
- {
- auto f1 = [](std::vector<int> &v,
- unsigned int left, unsigned int right) {
- unsigned long long acm = ;
- for (unsigned int i = left; i < right; ++i)
- {
- acm += v[i];
- }
- return acm;
- };
- auto t1 = std::async(f1, std::ref(v),
- , v.size() / );
- auto t2 = std::async(f1, std::ref(v),
- v.size() / , v.size());
- //You can do other things here!
- unsigned long long acm1 = t1.get();
- unsigned long long acm2 = t2.get();
- std::cout << "acm1: " << acm1 << endl;
- std::cout << "acm2: " << acm2 << endl;
- std::cout << "acm1 + acm2: " << acm1 + acm2 << endl;
- }
上面这段你需要知道:
1.tasks使用std::async创建
2.std::async的返回值是一个叫std::future的类型。别被他的名字唬到,他的意思是t1和t2的值会在未来被真正的赋值。我们通过调用t1.get()来获得他的真正的返回值。
3.如果future的返回值还没有准备好(任务还没有计算完成),那么调用get()的主线程会被卡住,直到准备好了返回值(和join()的行为一样)。
4.注意,我们传递给std::async的函数(实际上是lambda表达式)是有返回值的,这个返回值用过一个叫做std::promise的类型来传递。大多数情况下你不需要了解任何promise的细节,C++在幕后可以处理好这些事情。
5.默认的情况下,tasks也会在创建之后立刻运行(有办法来修改这个行为,但是本文没有涉及)。
线程创建的总结:
创建线程很简单,你可以通过函数指针、伪函数、lambda表达式的方式来创建std::thread,也可以使用std::async的方式来获得一个std::future类型的返回值。std::async也同样可以使用函数指针、伪函数、lambda表达式来创建
(未完待续)
5分钟速成C++14多线程编程的更多相关文章
- Siege——多线程编程最佳实例
在英语中,“Siege”意为围攻.包围.同时Siege也是一款使用纯C语言编写的开源WEB压测工具,适合在GNU/Linux上运行,并且具有较强的可移植性.之所以说它是多线程编程的最佳实例,主要原因是 ...
- JavaEE开发之Spring中的多线程编程以及任务定时器详解
上篇博客我们详细的聊了Spring中的事件的发送和监听,也就是常说的广播或者通知一类的东西,详情请移步于<JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换&g ...
- Siege(开源Web压力测试工具)——多线程编程最佳实例
在英语中,"Siege"意为围攻.包围.同时Siege也是一款使用纯C语言编写的开源WEB压测工具,适合在GNU/Linux上运行,并且具有较强的可移植性.之所以说它是多线程编程的 ...
- 《Java多线程编程实战指南(核心篇)》阅读笔记
<Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...
- Java—多线程编程
一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程.一个线程不能独立的存 ...
- Java多线程编程核心技术---对象及变量的并发访问(二)
数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...
- 转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可 ...
- Java多线程编程——进阶篇二
一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习: void notify() 唤醒在此对象监视器上等待的单个 ...
- 深入HTML5 Web Worker应用实践:多线程编程
HTML5 中工作线程(Web Worker)简介 至 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越多崭新的特性和功能.它不但强化了 Web 系统或网页的表现性能 ...
随机推荐
- CentOS7系列--2.2CentOS7中配置SSH服务
CentOS7配置SSH服务 1. SSH配置 1.1. 使用SSH服务更加安全 [root@centos7 ~]# vi /etc/ssh/sshd_config 设置如下 PermitRootLo ...
- WPF ListView ListBox 常用的样式记录
ListView: <ListView x:Name="lvBlockedApps" ItemsSource="{Binding BlockedAppsCollec ...
- ios虚拟机安装(二)
1)vmware快照功能 创建还原点:右键-->snapshot(对当前虚拟机系统备份) 2)虚拟机与外界系统通话 找到路径:/vmware/vmware workstation下的darwin ...
- RadioGroup实现类似ios的分段选择(UISegmentedControl)控件
在ios7中有一种扁平风格的控件叫做分段选择控件UISegmentedControl,控件分为一排,横放着几个被简单线条隔开的按钮,每次点击只能选择其中一个按钮,他类似于tabbar但是又稍微有点区别 ...
- Linux 启动脚本及chkconfig命令之自启动服务
有时我们会碰到这样的情况,系统启动的时候报一大堆无法连接mysql的错误,问题在mysql数据库还没有启动的时候已经启动了一些需要连接mysql数据库的服务.这样我们就得修改启动顺序,把需要连接mys ...
- sun.misc.BASE64Encoder在Eclipse中不能直接使用的原因和解决方案
1.为什么在Eclipse中不能直接使用sun.misc.BASE64Encoder和sun.misc.BASE64Decoder呢? 因为sun.misc.BASE64Encoder和sun.mis ...
- 短时傅里叶变换(Short Time Fourier Transform)原理及 Python 实现
原理 短时傅里叶变换(Short Time Fourier Transform, STFT) 是一个用于语音信号处理的通用工具.它定义了一个非常有用的时间和频率分布类, 其指定了任意信号随时间和频率变 ...
- oracle数据泵备份(Expdp命令)
Oracle备份方式主要分为数据泵导出备份.热备份与冷备份三种,今天首先来实践一下数据泵备份与还原.数据泵导出/导入属于逻辑备份,热备份与冷备份都属于物理备份.oracle10g开始推出了数据泵(ex ...
- Jmeter入门--工具组成和线程组
1.Jmeter工具组成部分: 资源生成器:用于生成测试过程中服务器,负载机的资源代码.(LoadRunner中的VuGen) 用户运行器:通常是一个脚本运行引擎,根据脚本要求模拟指定的用户行为.(L ...
- 使用Percona MySQL 5.7版本遇到的坑
监控DB由于使用的TokuDB引擎,因此选择使用Percona MySQL 5.7版本,在使用过程中遇到了比较多的坑,在这里做一下简单的记录,希望对广大DBA有帮助. load文件飙升导致的DB雪崩 ...