最近在看C++ STL库的多线程部分,基本上看完了,现在就来做一下总结吧。

目录

一、高级接口

二、低级接口

三、互斥量与锁(Mutex & Lock)

1、互斥量(Mutex):

2、锁(Lock):

四、条件变量(Condition Variable)

一、高级接口

std::async() & std::future

std::shared_future

二、低级接口

Class std::thread

std::promise

Class packaged_task

三、互斥量与锁(Mutex & Lock)

1、Class lock_guard

2、Class unique_lock

四、条件变量(Condition Variable)


一、高级接口

1、多线程启动函数:std::async()

2、线程返回结果:std::future

3、共享变量:std::shared_future

二、低级接口

1、多线程启动函数:Class std::thread

2、线程返回结果:Promise

3、线程池:Class packaged_task

三、互斥量与锁(Mutex & Lock)

1、互斥量(Mutex):

①概念

②作用

2、锁(Lock):

①概念

②作用

四、条件变量(Condition Variable)

①概念

②作用

下面让我们逐个来学习一下吧!

一、高级接口

std::async() & std::future

1、async()的作用在于将其获取到的函数立即在一个新的线程内进行异步启动。也就是一个线程启动函数。其形式如下:

std::aysnc(func1) //无参数形式

2、向async()传递启动函数,并且传入启动函数的参数:

void func1(int arg1,int arg2)
{std::cout<<arg1<<std::endl;std::cout<<arg2<<std::endl;
}std::aysnc(func1,arg1,arg2) //向函数func1传递arg1,arg2;

3、std::aysnc()会返回一个std::future object类型的返回值,在std::future object中,我们可以取得线程返回值或异常信息。此外,std::future object类型的特化与线程函数的返回值一致。形式如下:

void func1(int A = 0);
int func2();int main()
{std::future<void> func1_res(std::async(print_A,10)); //func1返回类型为void,future object的类型也为voidstd::future<int> func2_res(std::async(print_B));//func2返回类型为int,future object的类型也为int
}

4、指定std::aysnc()的发射策略(launch strategy)

std::async的策略主要有两个:

1、std::launch::async  :  立即尝试启动异步调用,如果在此处无法进行调用时,会返回一个std::system_error

2、std::launch::deferred : 延缓线程的启动,直到我们手动调用future::get()时,线程才会启动。

示例如下:

#include <future>
#include <ctime>
#include <iostream>
#include <Windows.h>void func1()
{std::cout << "func1 start!" << std::endl;
}
void func2()
{std::cout << "func2 start!" << std::endl;
}int main()
{//f1在这里就启动了,输出func1 start!std::future<void> f1(std::async(std::launch::async, func1));//f2在这里由于发射策略的原因,并没有启动std::future<void> f2(std::async(std::launch::deferred, func2));Sleep(3000);std::cout << "3 seconds later!" << std::endl;//三秒之后,由于调用future::get(),线程f2启动,输出func2 start!f2.get();return 0;
}

std::shared_future

shared_future 简单说来,其实就是一个可以多次调用 其成员函数get()的object。

由于std::future的成员函数get()只能够调用一次,第二次调用的时候会出现不可预期的行为(实际上就会报错或者完全不会有任何动作)。但是,很多时候,我们希望一个线程可以被多个线程利用,这个时候,std::share_future就横空出世了!

#include <future>
#include <iostream>
#include <string>
#include <thread>
#include <stdexcept>
#include <exception>using namespace std;
int func1()
{std::cout << "Read Number: ";int num;std::cin >> num;if (!std::cin){throw runtime_error("no number read");}return num;
}void addOne(std::shared_future<int> SfObject)
{int num = SfObject.get();num += 1;std::cout << num << std::endl;
}int main()
{std::shared_future<int> f = std::async(func1);auto f1 = std::async(addOne, f);auto f2 = std::async(addOne, f);f1.get();f2.get();return 0;
}

可以看到上面这段代码中,std::shared_future<int> f 的成员函数 std::get()被多次调用。假如我们将share_future object 换为futureobject时,甚至无法通过编译。

二、低级接口

Class std::thread

Class std::thread的调用接口与std::async()颇为显示,一起看一下下面这个实例:

#include <iostream>
#include <thread>void Print(int num)
{std::cout << "this is thread: "<< num << std::endl;
}int main()
{std::thread t1(Print, 1); //创建线程1std::thread t2(Print, 2); //创建线程2t1.join(); //等待线程1结束t2.join(); //等待线程2结束std::cout << "this is  main thread "<< std::endl;return 0;
}

可以看到,在线程创建和传参上,Class thread  和 std::async()的手法都颇为类似。只不过一个是类,一个是函数。

但是,两者也有颇多地方有较大差异:

1、Class thread 没有发射策略,只要我们实例化Class thread的对象,系统就会尝试启动目标函数,如果无法启动目标函数,就会抛出std::system_error并携带差错码resource_unavailable_try_again。

2、Class thread并不提供处理线程结果的接口

3、必须对线程的状态进行声明,等待其结束(join())或直接卸载(detach())

4、如果main()函数结束了,所有线程会被直接终止

std::promise

待补充。。

Class packaged_task

Class packaged_task实现了运行我们自由控制启动线程的启动时间,可以用于实现线程池。

让我们直接来看一个例子吧:

#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <future>
#include <Windows.h>void func1()
{std::cout << "creating thread……" << std::endl;
}int main()
{std::packaged_task<void()> task(func1);  //这里创建thread task,但是不会立即启动线程std::cout << "Sleep for 3 seconds" << std::endl;Sleep(3000); //sleep3秒,当然,这里可以改成任何你需要的操作task(); //3秒后启动线程return 0;
}

好的到目前为止,关于线程的启动和创建过程的内容到这里就基本结束了,接下来,我们以一张图作为这部分的结束。

(图源:《C++标准库》(侯捷译)

三、互斥量与锁(Mutex & Lock)

首先,我们得先理解为什么会出现互斥量这种需求。其实,在上面,有一段代码是在线程中输出一段字符串,我们看看看到,由于线程的启动都是同时的,所以两个不同的线程会同时对一个命令窗口输出字符,这样就会导致一种不可预计的情况,如图:

可以看到,字符的输出并没有按照我们的预期。那么,当我们有一个变量mutex,同时在多个线程中会被使用,其中线程A在线程B对mutex进行修改的过程中,同时又对mutex进行修改,那么,不可预期的事情便会发生。

所以,我们需要互斥量的出现。同时,我们需要对会被多个线程调用的变量的修改过程进行上锁(Lock),保证上锁过程中线程对资源的独占,才能避免多线程同时对某个变量进行修改,而导致不可预期的事情发生。

还是以上面的字符输出代码为例,我们要怎么修改才能得到我们预期的输出呢? 答案很简单,那就是对字符输出的过程进行上锁(lock),输出结束时解锁(unlock)。看代码吧!

#include <iostream>
#include <thread>
#include <mutex>std::mutex mut;//声明互斥量void Print(int num)
{mut.lock();//对输出过程进行上锁std::cout << "this is thread: "<< num << std::endl;mut.unlock();//解锁
}int main()
{std::thread t1(Print, 1);std::thread t2(Print, 2);t1.join();t2.join();std::cout << "this is  main thread "<< std::endl;return 0;
}

这样修改之后,就能得到我们想要得到的输出效果:

就是这样,我们对多线程处理的公共部分,进行上锁,使得线程独占资源,便可以使得资源在线程修改的这段时间内不被别的线程使用。但是,随着应用锁(Lock)的场景越来越多,我们也有了更多不同的需求,所以就发展出了各种不同的锁。同时也会出现一些问题。

(各种Mutex及其功能)

(Mutex Class 的操作函数)

接下来我们讲一下几个重要的Lock的方式

1、Class lock_guard

最简单的锁形式就是Mutex.Lock(),这个方法简单好用,但是有很多时候,人们会忘记将其解锁(Mutex.unlock()),所以出现了lock_guard的这种自动解锁的方法。Class lock_guard是在声明时,自动上锁,在离开作用域之后自动析构解锁。

我们看一下接口:

#include <iostream>
#include <thread>
#include <mutex>std::mutex mut;void Print(int num)
{std::cout << "this is thread_unlock: " <<num<< std::endl;//未上锁{std::lock_guard<std::mutex> lg(mut);//上锁std::cout << "this is thread: " << num << std::endl;}//超出作用域,自动解锁
}int main()
{std::thread t1(Print, 1);std::thread t2(Print, 2);t1.join();t2.join();std::cout << "this is  main thread " << std::endl;return 0;
}

上面的代码在没有上锁的字符输出过程中就串行了。

2、Class unique_lock

相对于Class lock_guard 来说,Class unique_lock 的特殊之处在于,可以让我们指定“何时”以及“如何”锁定和结果Mutex,此外,在Class unique_lock中,我们甚至可以用owns_lock()或bool()来查询目前Mutex是否会被锁住。

四、条件变量(Condition Variable)

在多线程的实际应用中,我们总有需要某个线程等待另外一个线程的处理结果。当然,最简单粗暴的方法自然就是设置一个全局的bool ReadyFlag,在 ReadyFlag 状态发生变化是,线程进行处理。

但是这样做有比较明显的弊端,举个栗子来说明这个问题吧~

bool ReadyFlag{False};void thread1()
{…………//大段处理代码,需要一定时间。ReadyFlag = true; //满足条件,ReadyFlag状态变为true
}void thread2()
{if(ReadyFlag){…………//大段处理代码}
}

可以看到在thread1函数中,当我们处理大段代码时,thread2中一直针对目标条件进行轮询,这样会耗费大量的资源。

实际上,我们希望得到的效果是: thread1 在处理完大量操作后,ReadyFlag的状态改变,达到满足 thread2 的启动条件,然后 thread1 将thread2 进行唤醒。

这就是条件变量(Condition Variable)存在的意义。

先来看一下Class Condition Variable的成员函数:

下面我们来看一个例子实际体会一下吧。

#include <condition_variable>
#include <mutex>
#include <future>
#include <iostream>bool readyFlag;
std::mutex readyMutex;
std::condition_variable readyCondVar;void thread1()
{std::cout << "<Return>" << std::endl;std::cin.get();{std::lock_guard<std::mutex> lg(readyMutex);readyFlag = true;}readyCondVar.notify_one();//条件成立,唤醒等待者
}void thread2()
{{std::unique_lock<std::mutex> ul(readyMutex);readyCondVar.wait(ul, [] {return readyFlag; });//等待条件变量的状态}std::cout << "Done!" << std::endl;
}int main()
{auto f1 = std::async(std::launch::async, thread1);auto f2 = std::async(std::launch::async, thread2);return 0;
}

使用条件变量(Condition Variable)进行这样的线程之间的通信等待,就可以放弃轮询操作,节省CPU的资源和时间。

好的,到这里为止,我对于C++多线程的总结就做到这里吧~感觉写了很多了。但是实际上,这些都只是一些皮毛,只能教会我们如何去使用多线程,对于多线程的应用和各种问题(比如我们常听到的“死锁”问题等)都没有深入探究,这些等我以后有空了再来谈吧。各位,如果看到有啥错误的地方,也请大家都能指出来,共同进步~

此外,我还推荐大家有空想学习多线程的,可以去看看《C++标准库》(侯捷译)这本书,里面对多线程的调用有极为详细的讲解。谢谢大家,看到这里。

C++ 多线程--STL库 总结版 (详细)相关推荐

  1. STL容器之Deque-《C++标准库(第二版)》读书笔记

    写在前面:本文是阅读<C++标准库(第二版)>的读书笔记. 文章目录 7.4 Deque 7.4.1 Deque的能力 7.4.2 Deque的操作函数 7.4.3 Exception H ...

  2. php dll是下32还是64位,windows下关于boost 64位和32位库的编译详细介绍

    常用编译命令: cd E:\OpenSource\boost_1_63_0\boost_1_63_0 编译32位库: x86环境下编译得先从开始菜单启动Visual Studio的Visual Stu ...

  3. Qt框架与STL库之间的巅峰对决:差异、优缺点及适用场景

    Qt框架与STL库之间的巅峰对决:差异.优缺点及适用场景 引言 对比的重要性 Qt框架与STL库简介 博客内容概览 Qt框架基础 Qt框架的特点与组成 Qt的信号槽机制 Qt容器类简介 数据结构的对比 ...

  4. 标准模板库之容器-《C++标准库(第二版)》读书笔记

    写在前面:本文是阅读<C++标准库(第二版)>的读书笔记. 文章目录 6.1 STL组件(Component) 6.2 容器(Container) 6.2.1 序列式容器(Sequence ...

  5. C++11 中STL库中新增内容

    C++ 11一个比较显著的变化是以前boost库中的一些函数被正式标准化合入到STL中了,本文就简单的介绍一下. 引用包装器(Reference Wrapper) 当模板函数参数为泛型类型的时候,无法 ...

  6. [c++]巧用stl库-啤酒与尿布

    [c++]<-巧用stl库-> 啤酒与尿布 前言 大家好,楼主计算机专业大学狗一枚.<啤酒与尿布>是专业课的一道课后习题,觉得我个人思路比较新颖,在此分享.能力一般,水平有限, ...

  7. 例题5-3安迪的第一个字典 UVa10815--C++STL库映射set的应用

    前言 不定期更新C++的STL库以及算法练习的笔记 分享给大家 也是督促自己不断努力学习算法与程序设计 学习算法之前,要想高效简洁的写好代码,还需要熟练掌握STL库的一些方法和数据结构 参考书籍: & ...

  8. C++17标准STL库并行策略在GCC编译器中的替代实现方法

    C++17标准STL库并行策略在GCC编译器中的替代实现方法 严正声明:本文系作者davidhopper原创,未经许可,不得转载.  2019年8月5日更新: GCC 9.1.0可支持C++ 17标 ...

  9. 百度地图安卓版详细接入流程解读(获取密钥详解)

    百度地图安卓版详细接入流程解读 一.接入Android地图SDK 1.1 功能介绍: 1.2 接入百度地图前的准备 1.1.1 访问官网,并下载开发包 1.1.2 获取开发密钥 1.1.3 项目环境搭 ...

  10. c语言 标准多线程库,关于C语言多线程pthread库的功能描述

    与线程相关的说明 һpthread_t pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthr ...

最新文章

  1. java post接口测试_接口测试——Java + TestNG 国家气象局接口(json解析)实例
  2. java 对象传递给方法_java – 将对象值传递给方法
  3. 软件测试与开发专业就业方向,学习软件测试后就业方向有哪些?
  4. [数据结构-严蔚敏版]P48栈的链式表示
  5. 警惕cocos2d-x Win32下资源命名大小写
  6. pcl之将QVTKWidget添加到QtCreator
  7. [转载] 使用python完成冒泡排序_使用python实现-冒泡排序
  8. Linux sz rz
  9. matlab语言与应用 10 数学问题的非传统解法
  10. java宽度优先搜索之八数码,广度优先搜索解决八数码问题
  11. 使用Win PE修改其他硬盘中的系统注册表
  12. Flutter按钮添加背景图片及文字
  13. Python前世今生
  14. mongodb误删数据该如何恢复
  15. js 和php 互操cookie 作用域
  16. 手绘线条一直画不直_板绘手不稳线画不直怎么办?
  17. CP和AP有是什么?有什么区别?
  18. 艾伟:一个让人遗忘的角落--Exception(一)
  19. Python 3.10 尝鲜
  20. 企微管家营销红包:给客户发红包,给客户群发红包均支持

热门文章

  1. AXI Memory-Mapped SRIO收发控制器
  2. 基于SRIO总线的高速通信
  3. html 星星字体,星星字,做华丽的星星文字效果
  4. 在自平衡中角动量守恒的原理分析
  5. QTableView 常用功能总结
  6. 旭日x3派,手势识别之Momo Quanghuang学习记录
  7. 基于SECS协议开发的简明教程(1)
  8. Gulp.js 参考手册,自动化构建利器
  9. 采用qt技术,开发OFD电子文档阅读器
  10. Excel 新手向个人练习-1 VLOOKUP,COUNTIF