C++并发编程
创始人
2024-03-23 08:54:20

目录

  • 一、并发编程相关的基础概念
    • 1、操作系统(Linux)
    • 2、任务和通信
    • 3、多进程和多线程
    • 4、C++中的多线程发展史
  • 二、pthread线程使用讲解和实战
    • 1、pthread基本使用
    • 2、线程的分离
    • 3、线程属性
    • 4、关于线程的几个值得注意的点
  • 三、线程的同步之互斥锁、读写锁、非阻塞式锁和条件变量
    • 1、线程同步的必要性
    • 2、互斥锁mutex
    • 3、读写锁
    • 4、非阻塞式锁
    • 5、条件变量
  • 四、标准库的thread基本使用
    • 1、标准库中线程支持
    • 2、std::thread的使用和案例分析
    • 3、管理当前线程的函数
  • 五、thread的线程同步
    • 1、mutex
    • 2、condition_variable
  • 六、thread的异步机制future
  • 七、C++20新引入的jthread

一、并发编程相关的基础概念

1、操作系统(Linux)

(1)内核和应用
  在Linux程序开发中,涉及到了许多的并发操作,根据程序的上下层关系可分为两部分,一部分是在内核中,即操作系统本身的在运行过程中就包含有大量的并发操作,如硬件驱动程序和内存管理以及进程调度模块就在同时运行着;另一部分就是我们经常说的应用程序,运行在linux系统之上的程序,如一个服务器应用程序,同时存在两个线程在运行,一个负责监听客户端,一个负责处理客户端的请求;

(2)进程和线程
  进程是系统进行资源分配的基本单位,线程是CPU进行调度的基本单位。一个进程中包含多个线程。有关于进程线程更多的区别和细节请自行百度,这里不再赘述。也可阅读我的另一个专栏《Linux IO编程和网络编程入门》;

(3)并行和串行,宏观和微观
  并行简而言之就是可以同一时间干很多件事情;串行就是有先后顺序,一件事情结束以后才能去做另一件事情。软件开发中的并行和串行也是如此,将一件事情替换为一个程序或者一个进程又或一个线程,能否同时运行;

  多个线程只有在多核处理器才能真正的同时运行,在单个处理器上只能做到假并行,看起来是在同时运行,实际上是调度器以时间片为单位切换调度执行多个线程,某一时刻只有一个程序在运行;

(4)系统调用,POSIX API,函数库、框架库

已经有人讲的很清楚了,我就不班门弄斧、制造垃圾了:
https://dandelioncloud.cn/article/details/1555512222984392706

(5)阻塞和非阻塞
  阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。
---------------来源于百度百科

2、任务和通信

(1)进程间通信IPC与线程间通信

进程间通信的方式:无名管道( pipe )、高级管道(popen)、有名管道(named pipe)、
消息队列( message queue )、信号量( semophore ) 、信号 ( sinal ) 、共享内存、
( shared memory ) 、套接字( socket )线程间通信的方式:互斥锁、读写锁、自旋锁、条件变量、信号机制、信号量机制

(2)同步和异步
  同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。

  异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。

  也可以认为,同步就是双方约定了一个固定的频率进行某件事情,如每天两点见面,则在两点的时候就不能去做其他事情了;而异步则是双方见面的时间不固定,想见了就见,没有固定约定的时间。

3、多进程和多线程

(1)如何选择使用多进程还是多线程

https://www.cnblogs.com/mude918/p/11750350.html

(2)单核和对称多核SMP下的多线程
  只有在多核下,才可以实现多个线程同时运行,在单核上只能实现伪并行;

4、C++中的多线程发展史

(1)C++98中没有并发支持,因为C中也没有并发支持,早期C++认为这不是语言该管的事儿

(2)POSIX OS的pthread被广泛用于C/C++的多线程编程

(3)这造成很大问题是:很多C++程序员根本没有并发编程的意识,需要时也只能盲目胡乱找资源

(4)Java在语言层面源生支持并发,取得了很大的成功和很好的反响

(5)C++11中开始引入并发编程机制std::thread

二、pthread线程使用讲解和实战

  对于二、三部分请阅读我之前写的一篇博客《linux线程全解
》,在本篇就不详述了,避免重复性工作。

  pthread与操作系统和编程语言无关,是符合posix标准的操作系统都具有的API

1、pthread基本使用

(1)ubuntu系统中进行 man 手册安装:
sudo apt-get install glibc-doc manpages-posix manpages-posix-dev(2)头文件:#include (3)链接时添加:-lpthread

2、线程的分离

(1)线程有2中状态:JOINABLE或者DETACHED,默认是JOINABLE

(2)JOINABLE的线程必须在创建它的线程中使用pthread_join回收,否则会有资源未释放

(3)DETACHED的线程可以在终止时释放资源,这样创建它的线程就不用通过pthread_join来等待接收

(4)线程转为DETACHED有2种方法:第一种是线程函数内自己调用pthread_detach(pthread_self());

3、线程属性

(1)pthread_attr_t attr;//声明一个参数
(2)pthread_attr_init(&attr);//对参数进行初始化
(3)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);//设置线程为可连接的
(4)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置线程为可分离的
(5)pthread_attr_destroy(&attr)//销毁属性,防止内存泄漏
(6)int pthread_attr_getdetachstate(pthread_attr_t *attr,int *detachstate);获取线程状态

4、关于线程的几个值得注意的点

(1)main所在的线程称之为“初始线程”,从main返回的时候,整个进程都被终止了;

(2)在任意线程内调用exit函数会让该线程所在的进程整个退出。所以主动退出线程的时候一定要使用pthread_exit函数,而不是exit;

(3)当主线程调用pthread_exit函数仅仅只是终止主线程,其他线程仍将继续存在;

三、线程的同步之互斥锁、读写锁、非阻塞式锁和条件变量

1、线程同步的必要性

  当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。

  举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?
  (1)账户余额是0,取钱不成功;(2)账户余额是100,取钱成功了。那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题,使得在同一时刻只有一个动作可以作用于这个对象身上;

2、互斥锁mutex

(1)互斥锁静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)互斥锁动态初始化:pthread_mutex_init(&mutex,NULL);
(3)上锁和解锁:pthread_mutex_lock(&mutex);	pthread_mutex_unlock(&mutex);
(4)互斥锁销毁:pthread_mutex_destroy(&mutex);

3、读写锁

pthread_rwlock_t
初始化:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
销毁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
进行读操作上锁:int pthread_rwlock_rdlock(pthread_rwlock_t  *rwlock);
进行写操作上锁:int pthread_rwlock_wrlock(pthread_rwlock_t  *rwlock);
解锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4、非阻塞式锁

(1)互斥锁非阻塞式上锁:pthread_mutex_trylock(&mutex);
(2)读写锁非阻塞式上锁:int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t  *rwlock);

5、条件变量

(1)条件变量的核心功能:A线程等待条件时阻塞wait,B线程必要时signal唤醒A
(2)条件变量实现了多个线程之间的同步
(3)条件变量常用于所谓的“生产者与消费者模型”
(4)条件变量相关API

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
pthread_mutex_lock(&mutex);/*锁住互斥量*/
pthread_cond_signal(&cond);//发送信号量 跟wait函数不在同一个线程中
pthread_cond_wait(&cond,&mutex);//阻塞线程,等待条件变量,同时解锁互斥量
pthread_mutex_unlock(&mutex);//解锁互斥量
pthread_mutex_destroy(&mutex);//销毁互斥锁
pthread_cond_destroy(&cond);//销毁条件变量

(5)条件变量的实例

参考:https://blog.csdn.net/chengonghao/article/details/51779279

四、标准库的thread基本使用

1、标准库中线程支持

(1)参考:https://zh.cppreference.com/w/cpp/thread
(2)C++11的实现是主体,C++20只是增加了扩展

2、std::thread的使用和案例分析

构造函数和传参:https://zh.cppreference.com/w/cpp/thread/thread/thread

类 thread 表示单个执行线程。线程允许多个函数同时执行。在头文件  定义;线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数
参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用std::terminate 。顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 
join 后),并且执行线程可能与任何 thread 对象无关( detach 后)。没有两个 std::thread 对象会表示同一执行线程; std::thread 不是可复制构造 
(CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 
(MoveConstructible) 且可移动赋值 (MoveAssignable) 。

在这里插入图片描述

// 左值引用
int num = 10;
int &b = num;     // 正确
int &c = 10;      // 错误int num = 10;
const int &b = num;   // 正确
const int &c = 10;    // 正确// 右值引用
int num = 10;
//int && a = num;    // 错误,右值引用不能初始化为左值
int && a = 10;       // 正确a = 100;
cout << a << endl;   // 输出为100,右值引用可以修改值// 右值引用的使用
// 如 thread argv 的传入
template
explicit thread(_Callable&& __f, _Args&&... __args) { 
//.... 
}
// Args&&... args 是对函数参数的类型 Args&& 进行展开
// args... 是对函数参数 args 进行展开
// explicit 只对构造函数起作用,用来抑制隐式转换

在这里插入图片描述

#include 
#include 
#include 
#include void f1(int n)
{for (int i = 0; i < 5; ++i) {std::cout << "Thread 1 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}void f2(int& n)
{for (int i = 0; i < 5; ++i) {std::cout << "Thread 2 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}
}class foo
{
public:void bar(){for (int i = 0; i < 5; ++i) {std::cout << "Thread 3 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int n = 0;
};class baz
{
public:void operator()(){for (int i = 0; i < 5; ++i) {std::cout << "Thread 4 executing\n";++n;std::this_thread::sleep_for(std::chrono::milliseconds(10));}}int n = 0;
};int main()
{int n = 0;foo f;baz b;std::thread t1; // t1 不是线程std::thread t2(f1, n + 1); // 按值传递std::thread t3(f2, std::ref(n)); // 按引用传递std::thread t4(std::move(t3)); // t4 现在运行 f2() 。 t3 不再是线程std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar()std::thread t6(b); // t6 在对象 b 的副本上运行 baz::operator()t2.join();t4.join();t5.join();t6.join();std::cout << "Final value of n is " << n << '\n';std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';std::cout << "Final value of b.n (baz::n) is " << b.n << '\n';
}
可能的输出:Thread 1 executing
Thread 2 executing
Thread 3 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Thread 2 executing
Thread 3 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 2 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Final value of n is 5
Final value of f.n (foo::n) is 5
Final value of b.n (baz::n) is 0

3、管理当前线程的函数

定义于命名空间 this_threadyield(C++11)建议实现重新调度各执行线程(函数)get_id (C++11)返回当前线程的线程 id(函数)sleep_for (C++11)使当前线程的执行停止指定的时间段(函数)sleep_until (C++11)使当前线程的执行停止直到指定的时间点(函数)

五、thread的线程同步

1、mutex

RAII风格:https://zh.cppreference.com/w/cpp/language/raii资源获取即初始化(Resource Acquisition Is Initialization),或称 RAII,是
一种 C++ 编程技术,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开
的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事
物)的生命周期绑定与一个对象的生存期相绑定。

在这里插入图片描述

2、condition_variable

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

#include 
#include 
#include 
#include 
#include std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;void worker_thread()
{// 等待直至 main() 发送数据std::unique_lock lk(m);cv.wait(lk, []{return ready;});// 等待后,我们占有锁。std::cout << "Worker thread is processing data\n";data += " after processing";// 发送数据回 main()processed = true;std::cout << "Worker thread signals data processing completed\n";// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )lk.unlock();cv.notify_one();
}int main()
{std::thread worker(worker_thread);data = "Example data";// 发送数据到 worker 线程{std::lock_guard lk(m);ready = true;std::cout << "main() signals data ready for processing\n";}cv.notify_one();// 等候 worker{std::unique_lock lk(m);cv.wait(lk, []{return processed;});}std::cout << "Back in main(), data = " << data << '\n';worker.join();
}
输出:main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing

六、thread的异步机制future

参考阅读:https://zh.cppreference.com/w/cpp/thread/future

在这里插入图片描述

七、C++20新引入的jthread

阅读参考:https://www.zhihu.com/question/364140779/answer/959369984
(1)std::jthread与std::thread的区别是什么?据我所知,特性上,std::jthread相比std::thread主要增加了以下两个功能:1.std::jthread对象被destruct时,会自动调用join,等待其所表示的执行流结束。2.支持外部请求中止(通过get_stop_source、get_stop_token和request_stop)。(2)为什么不是选择往std::thread添加新接口,而是引入了一个新的标准库?
因为std::jthread为了实现上述新功能,带来了额外的性能开销(主要是多了一个成员变量)
。而根据C++一直以来“不为不使用的功能付费”的设计哲学,他们自然就把这些新功能拆出来
新做了一个类。

关于《C++并发编程》后续还会增加章节的,这篇文章讲的很简略,更多的是让大家知道C++关于并发编程的一些库,避免后续工作或者学习中看到相关代码不知道来自于那里。本篇文章类似于预习课文的意思吧

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历、百度百科以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...