c++11多线程入门<学习记录>
最近学习了c++多线程相关知识,也算是对这方面内容的入门
视频链接c++11并发与多线程视频课程
看了大概两周,简单进行总结
参考文章C++11并发与多线程
PS:c++11提供了标准的可跨平台的线程库,本次多线程开发以此库为核心
一.并发,进程,线程理解
1.并发:两个或者更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务
2.进程:运行起来的可执行程序
3.线程:每个进程有唯一的主进程,可以通过写代码创建其他子线程
<一个新线程,代表一条新的代码执行路径>
二.子线程创建与结束
1.线程创建:thread类提供了创建线程的接口 #include <thread>
thread myThread(可调用对象); //创建myThread子线程,线程执行参数内代码(或者函数)
c++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象<称为线程函数>
2.线程结束
线程一旦创建,就会与主线程并发进行,此时如果主线程执行完毕,会强制退出程序,因此需要保证子线程在主线程之前执行完毕。库提供两种方式:
myThread.join(); //表示此时阻塞主线程,等待子线程执行完毕与主线程汇合,一起结束整个进程
myThread.detach(); //主线程不再与子线程汇合,不再等待子线程
//detach后,子线程和主线程失去关联,主调线程无法再取得该被调线程的控制权,驻留在后台,由C++运行时库接管
<由于detach使线程脱离主线程控制,若该线程使用了主线程或者其他线程中的内容,要注意内容是否有效合法,坑!>
三.线程ID和线程参数
1.线程ID:每一个线程都有自己独一无二的线程ID,可以用std::this_thread::get_id()来获取目前代码执行位置处于的线程ID
2.线程参数
thread myThread(print(), ...);
其中print为函数指针,后续参数就是传入print函数的参数,注意传参格式
四.多个线程创建,数据共享问题
1.多个线程创建
可以用STL容器来便于多个线程的创建,如std::verctor<thread>,不过要注意创建一个线程要给对应的join或者detach函数,让线程可控
2.数据共享
多线程程序要注意对共享数据的管理,由于线程执行顺序随机性,若对共享数据同时进行读和写,会导致读数据的一方得到脏数据,而写的一方修改混乱
<若线程都只读共享数据,可无需管理>
五.互斥量和死锁
1.互斥量 #include <mutex>
对共享数据的管理,最简便也是最佳方案就是用mutex锁进行管理
mutex类似一把锁,锁住共享数据部分,如果需要访问共享数据时,先要查看目前锁是否打开,若锁打开则进入访问数据,并且锁上防止其他线程进入,访问结束后解锁;若锁未打开则在外循环等待直到锁打开
std::mutex myMutex;
myMutex.lock();
//...共享数据
myMutex.unlock();
互斥量锁上了一定要记得打开,不然后续无法读取数据且线程会卡死
类似于指针,要释放内存。因此也有lock_guard类模板,和智能指针一样,创建时自动调用lock,作用域外自动调用unlock
std::lock_guard<std::mutex> myGuard(myMutex);
std::mutex myMutex;
{
std::lock_guard<std::mutex> myGuard(myMutex); //lock
//...共享数据
} //unlock
2.死锁
两个或两个以上的互斥量,由于在进程中锁的顺序不一样,导致两个或多个进程相互等待对方锁住的锁时,就产生了死锁。
<两个互斥量mutex1,mutex2。>
a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。
只要保证多个互斥量上锁的顺序一样就不会造成死锁。
六.unique_lock类模板
<很类似unique_ptr>
unique_lock可以取代lock_guard,理解为更加灵活的lock_guard,但效率相对较低
1.第二参数:
std::adopt_lock:表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了
std::try_to_lock:尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里
std::defer_lock:如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
2.常用成员函数:
lock():加锁
unlock():解锁
try_lock():尝试给互斥量加锁
如果拿不到锁,返回false,否则返回true。
release():解除与锁的绑定,返回它所管理的mutex对象的指针,并释放所有权
七.条件变量<condition_variable>
1.condition_variable:为一个类,为互斥量解锁设定条件
std::mutex mymutex1;
std::unique_lock<std::mutex> sbguard1(mymutex1);
std::condition_variable condition;
condition.wait(sbguard1, [this] {if (!msgRecvQueue.empty())
return true;
return false;
}); //锁 + 解锁条件
condition.wait(sbguard1); //不建议这么写
2.notify_one、notify_all
wait阻塞时,如果接收到其他地方的notify指令,则会尝试解锁.
a)如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥量已被锁住)
b)如果表达式为false,那wait又对互斥量解锁,然后又休眠,等待再次被notify_one()唤醒
<无第二参数默认为true>
PS:由于多线程执行随机性,可能会出现虚假notify,notify的时候wait线程不处于wait
notify_one():通知一个线程的wait()
notify_all():通知所有线程的wait()
八.async、future、packaged_task、promise
1.async、future <async支持接收函数返回值,future对象为接收者>
std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象
std::future对象,为类模板。
“future”将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到
std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。
std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和std::thread 的join()更像。
2.std::packaged_task:打包任务,把任务包装起来
为类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
int mythread(int mypar){...}
std::packaged_task<int(int)> mypt(mythread);
//用法一
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
//用法二,直接调用
mypt(1);
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
3.std::promise类模板
我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来
九.future其他成员函数、shared_future、atomic
1.future其他成员函数
std::future_status status = result.wait_for(std::chrono::seconds(5s));
卡住当前流程,等待std::async()的异步任务运行一段时间,然后返回其状态std::future_status。
std::future_status是枚举类型,表示异步任务的执行状态。类型的取值有
std::future_status::timeout //时间耗尽,还未运行结束
std::future_status::ready //运行结束
std::future_status::deferred //async为deferred状态
2.std::shared_future:也是个类模板
std::future的 get() 成员函数是转移数据
std::shared_future 的 get()成员函数是复制数据
3.std::atomic原子操作<“不可分割的操作”>
原子操作,指的是执行该操作时,CPU不会强制切换时间片,必须等该操作完全执行完成,才会切换时间片,因此可以保护数据合法性。
和互斥量类似,但从效率上来说,原子操作要比互斥量的方式效率要高。
互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。
#include <atomic>
std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
void mythread1() {
for (int i = 0; i < 1000000; i++) {
g_count++;
}
}
nt main() {
std::thread t1(mythread1);
std::thread t2(mythread1);
t1.join();
t2.join();
cout << "正常情况下结果应该是次,实际是" << g_count << endl; //2000000
}
十.std::atomic续谈、std::async深入谈
1.一般atomic原子操作,针对++,--,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。
2.std::async深入理解
第二参数:
std::launch::deferred【延迟调用】
std::launch::async【强制创建一个线程】
如果同时用 std::launch::async | std::launch::deferred这里这个 | 意味着async的行为可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案<若无第二参数,这种为默认值>
async不确定性问题的解决
不加额外参数的async调用时让系统自行决定,是否创建新线程。
std::future result = std::async(mythread);
问题焦点在于这个写法,任务到底有没有被推迟执行。
通过wait_for返回状态来判断:
std::future_status status = result.wait_for(std::chrono::seconds(6));
//std::future_status status = result.wait_for(6s);
if (status == std::future_status::timeout) {
//超时:表示线程还没有执行完
cout << "超时了,线程还没有执行完" << endl;
}
else if (status == std::future_status::ready) {
//表示线程成功放回
cout << "线程执行成功,返回" << endl;
cout << result.get() << endl;
}
else if (status == std::future_status::deferred) {
cout << "线程延迟执行" << endl;
cout << result.get() << endl;
}
十一. windows临界区、其他各种mutex互斥量
1.windows临界区
Windows临界区,同一个线程是可以重复进入的,但是进入的次数与离开的次数必须相等。
C++互斥量则不允许同一个线程重复加锁。
#include <Windows.h>
CRITICAL_SECTION my_winsec; //windows中的临界区,非常类似C++11中的mutex
InitializeCriticalSection(&my_winsec); //用临界区之前要初始化
EnterCriticalSection(&my_winsec); //进入临界区
// ....
LeaveCriticalSection(&my_winsec); //离开临界区
2.其他各种mutex互斥量
递归独占互斥量 std::recursive_mutex:允许在同一个线程中同一个互斥量多次被 lock() ,(但是递归加锁的次数是有限制的,太多可能会报异常),效率要比mutex低。
带超时的互斥量 std::timed_mutex 和 std::recursive_timed_mutex:判断是否拿到锁,若未拿到可以继续执行其他代码
十二.线程池浅谈、线程数量谈
1.线程池
把一堆线程弄到一起,统一管理。这种统一管理调度,循环利用的方式,就叫做线程池。
实现方式:程序启动时,一次性创建好一定数量的线程。这种方式让人更放心,觉得程序代码更稳定。
2.数量谈
A、线程创建的数量极限的问题
一般来讲,2000个线程基本就是极限;再创建就会崩溃。
B、线程创建数量建议
a、采用某些计数开发程序提供的建议,遵照建议和指示来确保程序高效执行。
b、创建多线程完成业务;考虑可能被阻塞的线程数量,创建多余最大被阻塞线程数量的线程,如100个线程被阻塞再充值业务,开110个线程就是很合适的
c、线程创建数量尽量不要超过500个,尽量控制在200个之内;
END<信号量呢??>
c++11多线程入门<学习记录>的更多相关文章
- java多线程入门学习(一)
java多线程入门学习(一) 一.java多线程之前 进程:每一个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销.一个进程包括1--n个线程. 线程:同一类线程共享代码 ...
- redis入门学习记录(二)
继第一节 redis入门学习记录(一)之后,我们来学习redis的基本使用. 接下来我们看看/usr/local/redis/bin目录下的几个文件作用是什么? redis-benchmark:red ...
- c++11 多线程入门教程(一)
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/10945309.html 最近在找c++服务端开发的实习(大佬们有推荐吗QAQ..),恰好写了一 ...
- gulp入门学习教程(入门学习记录)
前言 最近在通过教学视频学习angularjs,其中有gulp的教学部分,对其的介绍为可以对文件进行合并,压缩,格式化,监听,测试,检查等操作时,看到前三种功能我的心理思想是,网上有很多在线压缩,在线 ...
- SpringBoot入门学习记录(一)
最近,SpringBoot.SpringCloud.Dubbo等框架非常流行,作为Coder里的一名小学生,借着改革开放的东风,自然也是需要学习学习的,于是将学习经历记录于此,以备日后查看. 官网:h ...
- scikit-learn入门学习记录
一加载示例数据集 from sklearn import datasets iris = datasets.load_iris() digits = datasets.load_digits() 数据 ...
- mybatis入门学习记录(一)
过硬的技术本领,可以给我们保驾护航,飞得更高.今天开始呢.我们就一起来探讨使用mybatis的好处. 首先我们一起来先看看原生的JDBC对于数据库的操作,然后总结其中的利弊,为学习mybatis奠定基 ...
- Sentinel入门学习记录
最近公司里面在进行微服务开发,因为有使用到限流降级,所以去调研学习了一下Sentinel,在这里做一个记录. Sentinel官方文档:https://github.com/alibaba/Senti ...
- Python3.5入门学习记录-File
在Python中,操作文件对象使用open函数来创建,下表列出了常用的操作file的函数: 序号 方法及描述 1.file.close() 关闭文件.关闭后文件不能再进行读写操作. 2.file.fl ...
- [2017.02.07] Lua入门学习记录
#!/home/auss/Projects/Qt/annotated/lua -- 这是第一次系统学习Lua语言 --[[ 参考资料: 1. [Lua简明教程](http://coolshell.cn ...
随机推荐
- Camera | 11.瑞芯微摄像头采集图像颜色偏绿解决笔记
前言 在实际调试基于瑞芯微平台的camera过程中,发现显示的图片发绿, 现在把调试步骤分享给大家: 1.修改iq文件 sdk中位置: @external/camera_engine_rkaiq/iq ...
- ARMv8-A 地址翻译技术之MMU的前世今生
MMU的重要性不言而喻,支撑操作系统之上的各种复杂应用.但在正式讲MMU之前,我们先说说MMU的发展史,因为ARMv8-A的MMU相当复杂,直接切入正题,会显得比较枯燥.废话不多说,咱们马上开始: 一 ...
- Windows添加软件开机自启动
两种方式 1.添加快捷方式到开始菜单 打开我的电脑找到C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup 文件夹, 如果难找的话可 ...
- Docker 发布镜像
发布镜像 在 Docker Hub 发布镜像 登陆到 Docker Hub docker login 标记镜像并推送到 Docker Hub docker tag <image>:< ...
- 【YashanDB知识库】列与存储过程中重名变量/别名问题
问题现象 当一条查询中出现了重复别名,或者在一个存储过程中出现了变量名称与查询中别名相同,就会报错.这个问题在多个客户现场出现. create table test_tab1 (c1 int, c2 ...
- ComfyUI 基础教程(二) —— Stable Diffusion 文生图基础工作流及常用节点介绍
上一篇文章讲解述首次启动 ComfyUI 会自动打开一个最基础的文生图工作流.实际上,后续我们可以通过菜单选项,或者快捷键 ctrl + D来打开这个默认工作流.默认工作流如下: 这是一个最基础的文生 ...
- C#/.NET/.NET Core优秀项目和框架2024年8月简报
前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍.功能特点.使用方式以及部分功能截图 ...
- 外挂级OCR神器:免费文档解析、表格识别、手写识别、古籍识别、PDF转Word
TextIn Tools是一款免费的在线OCR工具,支持快速准确的文字和表格识别,手写.古籍识别,提供PDF转Markdown大模型辅助工具,同时支持PDF.WORD.EXCEL.JPG.PPT等各类 ...
- RxJS 系列 – Utility Operators
前言 前几篇介绍过了 Creation Operators Filtering Operators Join Creation Operators Error Handling Operators T ...
- HTML——简介-入门
W3C标准:网页主要由三部分组成 结构:HTML 表现:CSS 行为:JavaScript HTML快速入门 1.新建文本文件,后缀改为 .html 2.编写HTML结构标签(不区分大小写) ...