0.引言
测试用例CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(thread_learn)
add_compile_options(-g)
SET(CMAKE_CXX_COMPILER "g++")
set( CMAKE_BUILD_TYPE Debug )
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -march=native -O3 -pthread" )
find_package (Threads REQUIRED)
include_directories(
${PROJECT_SOURCE_DIR}
)
add_executable(thread_learn thread_learn.cpp )
一.基本概念和实现
- 0.目录
1.1.并发、进程、线程的基本概念
并发,线程,进程要求必须掌握
1.1.1 并发
两个或者更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务;
- 单核CPU:某一个时刻只能执行一个任务,由操作系统调度,每秒钟进行多次所谓的“任务切换”。并发的假象(不是真正的并发),切换(上下文切换)时要保存变量的状态、执行进度等,存在时间开销;
- 多核CPU:用于服务器和高性能计算领域。台式机:在一块芯片上有多核(一个CPU内有多个运算核心,对于操作系统来说,每个核心都是作为单独的CPU对待的):双核,4核,8核,10核(自己的笔记本是4核8线程的)。能够实现真正的并行执行多个任务(硬件并发)
使用并发的原因:主要就是同时可以干多个事,提高性能
1.1.2 可执行程序
磁盘上的一个文件,windows下,扩展名为.exe;linux下,ls -la,rwx(可读可写可执行)
1.1.3 进程
运行一个可执行程序,在windows下,可双击;在linux下,./文件名 进程,一个可执行程序运行起来了,就叫创建了一个进程。进程就是运行起来的可执行程序。
1.1.4 线程
-
① a)每个进程(执行起来的可执行程序),都有唯一的一个主线程 b)当执行可执行程序时,产生一个进程后,这个主线程就随着这个进程默默启动起来了 c)ctrl+F5运行这个程序的时候,实际上是进程的主线程来执行(调用)这个main函数中的代码 线程:用来执行代码的。线程东西,可以理解为一条代码的执行通路。 -
② a)除了主线程之外,可以通过写代码来创建其他线程,其他线程走的是别的道路,甚至去不同的地方 b)每创建一个新线程,就可以在同一时刻,多干一个不同的事(多走一条不同的代码执行路径) -
③ a)多线程(并发) b)线程并不是越多越好,每个线程,都需要一个独立的堆栈空间(大约1M),线程之间的切换要保存很多中间状态,切换也会耗费本该属于程序运行的时间 c)必须使用多线程的案例
1.1.5 学习心得
- 线程是用来执行代码的;
- 一个线程就是一个执行通路,一个新线程代表一条新的通路;
- 一个进程自动包含一个主线程,主线程随着进程默默的启动并运行,我们可以通过编码来创建多个其他线程(非主线程)。但是创建的线程数量不建议超过200个;
- 因为主线程是自动启动的,所以一个进程中最少也是有一个线程的(主线程),进程和线程感觉是爹和儿子的关系;
1.2.并发的实现方法
实现并发的手段:
- a)通过多个进程实现并发
- b)在单独的进程中,写代码创建除了主线程之外的其他线程来实现并发
1.2.1 多进程并发
- 比如账号服务器一个进程,游戏服务器一个进程。
- 服务器进程之间存在通信(同一个电脑上:管道,文件,消息队列,共享内存);(不同电脑上:socket通信技术)
1.2.2 多线程并发
- 线程:感觉像是轻量级的进程。每个进程有自己独立的运行路径,但一个进程中的所有线程共享地址空间(共享内存),全局变量、指针、引用都可以在线程之间传递,所以多线程开销远远小于多进程。共享内存带来的新问题,数据一致性问题:线程A、线程B,不能同时写。
- 多进程并发和多线程并发可以混合使用,但建议优先考虑多线程技术
- 本课程中只讲多线程并发技术
1.2.3.总结
- 和进程相比,线程具有如下优点:
- 1.线程启动速度更快,更轻量级;
- 2.系统资源开销更少,执行速度更快,比如共享内存这种通信方式比任何其他的通信方式都快
- 缺点:
1.3.C++11新标准线程库
以往
- windows:CreateThread(), _beginthread(),_beginthreadexe()创建线程;
- linux:pthread_create()创建线程;
- 概念:临界区,互斥锁
- 不能跨平台
- 多线程库可跨平台:POSIX thread(pthread):跨平台,但要做一番配置,也不方便
C++11
- 从C++11新标准,C++语言本身增加对多线程的支持,意味着可移植性(跨平台),这大大减少开发人员的工作量
二.线程启动、结束,创建线程多法、join,detach
2.1.范例演示线程运行的开始
- 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;当主线程从main()函数返回,则整个进程执行完毕
- 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行
- 整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了,此时如果其他子线程还没有执行完,也会被强行终止【此条有例外,以后会解释】
创建一个线程:
- 1.包含头文件thread
- 2.写初始函数
- 3.在main中创建thread
必须要明白:有两个线程在跑,相当于整个程序中有两条线在同时走,即使一条被阻塞,另一条也能运行
#include <iostream>
#include <thread>
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
thread myThread(myPrint);
cout << "Hello World!" << endl;
return 0;
}
多次运行结果:
Hello World!我的线程开始运行
terminate called without an active exception
Aborted (core dumped)
--------
Hello World!
我的线程开始运行
我的线程运行完毕
terminate called without an active exception
Aborted (core dumped)
--------
Hello World!
我的线程开始运行terminate called without an active exception
我的线程运行完毕
Aborted (core dumped)
--------
我的线程开始运行
我的线程运行完毕
Hello World!
terminate called without an active exception
Aborted (core dumped)
--------
Hello World!
terminate called without an active exception
我的线程开始运行
我的线程运行完毕
Aborted (core dumped)
两个线程同时运行,谁快谁慢不一定。terminate called without an active exception 报错是因为,main函数主线程运行完了,myprint线程还没运行完导致,即主线程结束,子线程未结束;。
#include <iostream>
#include <thread>
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
多次运行结果均为:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完
#include <iostream>
#include <thread>
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
myThread.detach();
cout << "Hello World-2!" << endl;
return 0;
}
join与detach不能同时使用,顾名思义,一个分离一个汇合,肯定无法同时使用。否则报错:
terminate called after throwing an instance of 'std::system_error'
what(): Invalid argument
Aborted (core dumped)
多次运行结果:
Hello World-1!
Hello World-2!
我的线程开始运行
我的线程运行完毕
--------
Hello World-1!我的线程开始运行
我的线程运行完毕
Hello World-2!
--------
Hello World-1!我的线程开始运行
Hello World-2!
我的线程运行完毕
--------
Hello World-1!我的线程开始运行
我的线程运行完毕
Hello World-2!
--------
Hello World-1!
我的线程开始运行Hello World-2!
我的线程运行完毕
--------
Hello World-1!
Hello World-2!我的线程开始运行
--------
Hello World-1!
Hello World-2!
--------
#include <iostream>
#include <thread>
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
if (myThread.joinable()) {
cout << "可以调用可以调用join()或者detach()" << endl;
} else {
cout << "不能调用可以调用join()或者detach()" << endl;
}
cout << "Hello World-2!" << endl;
return 0;
}
多次调用结果:
--------
Hello World-1!
可以调用可以调用join()或者detach()
Hello World-2!
terminate called without an active exception
Aborted (core dumped)
--------
Hello World-1!我的线程开始运行
可以调用可以调用join()或者detach()
Hello World-2!
terminate called without an active exception
我的线程运行完毕
Aborted (core dumped)
--------
Hello World-1!我的线程开始运行
我的线程运行完毕
可以调用可以调用join()或者detach()
Hello World-2!
terminate called without an active exception
Aborted (core dumped)
--------
Hello World-1!我的线程开始运行
可以调用可以调用join()或者detach()
我的线程运行完毕
Hello World-2!
terminate called without an active exception
Aborted (core dumped)
-------
terminate called without an active exception 报错是因为,main函数主线程运行完了,myprint线程还没运行完导致,即主线程结束,子线程未结束;。
#include <iostream>
#include <thread>
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
myThread.join();
if (myThread.joinable()) {
cout << "可以调用可以调用join()或者detach()" << endl;
} else {
cout << "不能调用可以调用join()或者detach()" << endl;
}
cout << "Hello World-2!" << endl;
return 0;
}
多次调用结果均为:
Hello World-1!
我的线程开始运行
我的线程运行完毕
不能调用可以调用join()或者detach()
Hello World-2!
重要补充:线程类参数是一个可调用对象。
- 一组可执行的语句称为可调用对象,c++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象。
2.2.其他创建线程的方法
- ①创建一个类,并编写圆括号重载函数,初始化一个该类的对象,把该对象作为线程入口地址
#include <iostream>
#include <thread>
using namespace std;
class Ta {
public:
void operator()()
{
cout << "我的线程开始运行" << endl;
cout << "我的线程运行完毕" << endl;
}
};
int main() {
cout << "Hello World-1!" << endl;
Ta ta;
thread myThread(ta);
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
运行结果:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
#include <iostream>
#include <thread>
using namespace std;
auto lambdaThread = [] {
cout << "我的线程开始执行了" << endl;
cout << "我的线程开始执行了" << endl;
};
int main() {
cout << "Hello World-1!" << endl;
thread myThread(lambdaThread);
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
运行结果:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
#include <iostream>
#include <thread>
using namespace std;
class Data_ {
public:
void GetMsg() { std::cout << "fun GetMsg is run!\n"; }
void SaveMsh() { std::cout << "fun SaveMsh is run!!\n"; }
};
int main() {
cout << "Hello World-1!" << endl;
Data_ s;
thread oneobj(&Data_::SaveMsh, &s);
thread twoobj(&Data_::GetMsg, &s);
oneobj.join();
twoobj.join();
cout << "Hello World-2!" << endl;
return 0;
}
运行结果:
Hello World-1!
fun SaveMsh is run!!
fun GetMsg is run!
Hello World-2!
三.线程传参详解,detach()大坑,成员函数做线程函数
3.1.传递临时对象作为线程参数
#include <iostream>
#include <thread>
using namespace std;
void myPrint(const int& i, char* pmybuf) {
cout << i << endl;
cout << pmybuf << endl;
}
int main() {
int mvar = 10;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar, mybuf);
mybuf[0] = 'T';
myThread.join();
mybuf[0] = 't';
cout << "Hello World!" << endl;
}
运行结果:
10
This is a test
Hello World!
为什么是大写,参考:
#include <iostream>
#include <string>
#include <thread>
using namespace std;
void myPrint(const int i, const string& pmybuf) {
cout << i << endl;
cout << pmybuf << endl;
}
int main() {
int mvar = 10;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar, mybuf);
myThread.detach();
cout << "Hello World!" << endl;
}
多次运行结果,结果不可控:
Hello World!
10
this is a test
--------
Hello World!
10
--------
Hello World!
10
this is a test
--------
Hello World!
10
this is a test
#include <iostream>
#include <string>
#include <thread>
using namespace std;
void myPrint(const int i, const string& pmybuf) {
cout << i << endl;
cout << pmybuf << endl;
}
int main() {
int mvar = 10;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar,string(mybuf));
myThread.detach();
cout << "Hello World!" << endl;
}
多次运行结果:
Hello World!
--------
Hello World!10
this is a test
--------
Hello World!
10
this is a test
--------
Hello World!
10
this is a test
3.2.临时对象作为线程参数继续讲
#include <iostream>
#include <string>
#include <thread>
using namespace std;
void myPrint(const int i, const string& pmybuf) {
cout << i << endl;
cout << pmybuf << endl;
cout << std::this_thread::get_id() << std::endl;
}
int main() {
int mvar = 10;
char mybuf[] = "this is a test";
thread myThread(myPrint, mvar, string(mybuf));
cout << std::this_thread::get_id() << std::endl;
myThread.join();
cout << "Hello World!" << endl;
}
多次运行结果:
139715575359296
10
this is a test
139715533989632
Hello World!
--------
139966551197504
10
this is a test
139966509827840
Hello World!
--------
140299849795392
10
this is a test
140299808425728
Hello World!
3.3.传递类对象、智能指针作为线程参数
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
mutable int m_i;
A(int i) : m_i(i) {}
};
void myPrint(const A& pmybuf) {
pmybuf.m_i = 199;
cout << "子线程myPrint的参数地址是" << &pmybuf << endl
<< "thread = " << std::this_thread::get_id() << endl;
}
int main() {
A myObj(10);
thread myThread(myPrint, myObj);
myThread.join();
cout << "Hello World!" << endl;
}
运行结果:
子线程myPrint的参数地址是0x55aac9ce2e78
thread = 140061621405440
Hello World!
#include <iostream>
#include <memory>
#include <thread>
using namespace std;
void myPrint(unique_ptr<int> ptn) {
cout << "thread = " << std::this_thread::get_id() << endl;
}
int main() {
unique_ptr<int> up(new int(10));
thread myThread(myPrint, std::move(up));
myThread.join();
return 0;
}
运行结果:
thread = 139634421532416
四.创建多个线程、数据共享问题分析、案例代码
4.1.创建和等待多个线程
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
using namespace std;
void TextThread() {
cout << "我是线程" << this_thread::get_id() << endl;
cout << "线程" << this_thread::get_id() << "执行结束" << endl;
}
int main() {
std::vector<thread> threadagg;
for (int i = 0; i < 5; ++i) {
threadagg.push_back(thread(TextThread));
}
cout<<"hello!\n";
for (int i = 0; i < 5; ++i) {
threadagg[i].join();
}
return 0;
}
运行结果:
我是线程我是线程140472505550592140472497157888
线程140472505550592执行结束
我是线程我是线程140472408340224
线程140472408340224
hello!
140472488765184
线程140472488765184执行结束
线程140472497157888执行结束
执行结束
我是线程140472399947520
线程140472399947520执行结束
--------
我是线程我是线程140031451133696140031467919104
我是线程140031368886016
线程线程140031451133696执行结束
140031368886016执行结束
hello!
线程140031467919104执行结束
我是线程140031459526400
线程140031459526400执行结束
我是线程140031360493312
线程140031360493312执行结束
- 把thread对象放入到容器中管理,看起来像个thread对象数组,对一次创建大量的线程并对大量线程进行管理有好处
- 但实际上多个线程执行顺序是乱的,跟操作系统内部对线程的运行调度机制有关
4.2.数据共享问题分析
- 只读的数据:是安全稳定的
- 有读有写:若不加处理,就会出错,最简单的防止崩溃方法:读的时候不能写,写的时候不能读。写的动作分10小步,由于任务切换,导致各种诡异的事情发生(最可能的还是崩溃)
五.互斥锁概念、用法、死锁演示及解决详解
- mutex类4种
- std::mutex,最基本的 Mutex 类。
- std::recursive_mutex,递归 Mutex 类。
- std::time_mutex,定时 Mutex 类。
- std::recursive_timed_mutex,定时递归 Mutex 类。
- Lock 类(两种)
- std::lock_guard,与 Mutex RAII 相关,方便线程对互斥锁上锁。
- std::unique_lock,与 Mutex RAII 相关,方便线程对互斥锁上锁,但提供了更好的上锁和解锁控制。
- 其他类型
- std::once_flag
- std::adopt_lock_t
- std::defer_lock_t
- std::try_to_lock_t
- 函数
- std::try_lock,尝试同时对多个互斥锁上锁。
- std::lock,可以同时对多个互斥锁上锁。
- std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
5.1.互斥锁(mutex)的基本概念
- 互斥锁就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
- 互斥锁使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。
std::mutex 的成员函数
- 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
- lock(),调用线程将锁住该互斥锁。线程调用该函数会发生下面 3 种情况:
- (1). 如果该互斥锁当前没有被锁住,则调用线程将该互斥锁锁住,直到调用 - unlock之前,该线程一直拥有该锁。
- (2). 如果当前互斥锁被其他线程锁住,则当前的调用线程被阻塞住。
- (3). 如果当前互斥锁被当前调用线程锁住,则会产生死锁(deadlock)。
- unlock(), 解锁,释放对互斥锁的所有权。
- try_lock(),尝试锁住互斥锁,如果互斥锁被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,
- (1). 如果当前互斥锁没有被其他线程占有,则该线程锁住互斥锁,直到该线程调用 unlock 释放互斥锁。
- (2). 如果当前互斥锁被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
- (3). 如果当前互斥锁被当前调用线程锁住,则会产生死锁(deadlock)。
5.2.互斥锁的用法
包含#include <mutex> 头文件
5.2.1 lock(),unlock()
- 步骤:1.lock(),2.操作共享数据,3.unlock()
- lock()和unlock()要成对使用
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void print_block(int n, char c) {
for (int i = 0; i < n; ++i) {
std::cout << c;
}
std::cout << '\n';
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
运行结果不可控:
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$*$***$****$*$$$$$*$$$******$*$*$$$*$
*
--------
*******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
*******************
--------
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
**************************************************
--------
*******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
*******************
加锁:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void print_block(int n, char c) {
mtx.lock();
for (int i = 0; i < n; ++i) {
std::cout << c;
}
std::cout << '\n';
mtx.unlock();
}
int main() {
std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
运行结果:
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
**************************************************
--------
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
再看一个例子,两个线程竞争全局变量g_num对其进行写操作,然后打印输出:
#include <iostream>
#include <mutex>
#include <thread>
int g_num = 0;
std::mutex g_num_mutex;
void slow_increment(int id) {
for (int i = 0; i < 3; ++i) {
++g_num;
std::cout << "th" << id << " => " << g_num << '\n';
}
}
int main() {
std::thread t1(slow_increment, 0);
std::thread t2(slow_increment, 1);
t1.join();
t2.join();
}
运行结果:
thth0 => 2
th0 => 3
th0 => 4
1 => 4
th1 => 5
th1 => 6
--------
thth1 => 02
th1 => 3
th1 => 4
=> 4
th0 => 5
th0 => 6
--------
thth0 => 2
th0 => 3
th0 => 4
1 => 4
th1 => 5
th1 => 6
#include <iostream>
#include <mutex>
#include <thread>
int g_num = 0;
std::mutex g_num_mutex;
void slow_increment(int id) {
for (int i = 0; i < 3; ++i) {
g_num_mutex.lock();
++g_num;
std::cout << "th" << id << " => " << g_num << '\n';
g_num_mutex.unlock();
}
}
int main() {
std::thread t1(slow_increment, 0);
std::thread t2(slow_increment, 1);
t1.join();
t2.join();
}
运行结果:
th0 => 1
th0 => 2
th0 => 3
th1 => 4
th1 => 5
th1 => 6
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
int g_num = 0;
std::mutex g_num_mutex;
void slow_increment(int id) {
for (int i = 0; i < 3; ++i) {
g_num_mutex.lock();
++g_num;
std::cout << "th" << id << " => " << g_num << '\n';
g_num_mutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
std::thread t1(slow_increment, 0);
std::thread t2(slow_increment, 1);
t1.join();
t2.join();
}
运行结果
th0 => 1
th1 => 2
th0 => 3
th1 => 4
th1 => 5
th0 => 6
--------
th0 => 1
th1 => 2
th0 => 3
th1 => 4
th0 => 5
th1 => 6
--------
th0 => 1
th1 => 2
th0 => 3
th1 => 4
th0 => 5
th1 => 6
5.2.2 lock_guard类模板
- lock_guard sbguard(myMutex);取代lock()和unlock()
- lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock()
lock_guard类模板可直接取代lock( )、unlack( ),并且使用lock_guard类模板后不能再对这个保护区域使用lock( )、unlack( ).?lock_guard类模板超出作用域时会自动析构释放锁。
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
int g_num = 0;
std::mutex g_num_mutex;
void slow_increment(int id) {
for (int i = 0; i < 3; ++i) {
std::lock_guard<std::mutex> m_guard(g_num_mutex);
++g_num;
std::cout << "th" << id << " => " << g_num << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
std::thread t1(slow_increment, 0);
std::thread t2(slow_increment, 1);
t1.join();
t2.join();
}
运行结果:
th0 => 1
th0 => 2
th0 => 3
th1 => 4
th1 => 5
th1 => 6
--------
th1 => 1
th1 => 2
th1 => 3
th0 => 4
th0 => 5
th0 => 6
--------
th0 => 1
th0 => 2
th0 => 3
th1 => 4
th1 => 5
th1 => 6
5.2.3 std::lock_guard的std::adopt_lock参数
-
std::lock_guard<std::mutex> my_guard(my_mutex,std::adopt_lock); 加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock(); -
adopt_guard为结构体对象,起一个标记作用,表示这个互斥锁已经lock(),不需要再lock()。 -
原博文和这篇好像
5.3.死锁
5.3.1 死锁演示
死锁至少有两个互斥锁mutex1,mutex2。
- a.线程A执行,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
- b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
- c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。
5.3.2 死锁的一般解决方案:
1、以确定的顺序获得锁
按照上面的例子,两个线程获得锁的时序图如下:
如果此时把获得锁的时序改成:
那么死锁就永远不会发生。
2、超时放弃
该方法可以按照固定时长等待锁,线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
死锁例子:
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
class JackTang {
public:
int number;
JackTang(int a) : number(a) {}
JackTang(const JackTang &obj) { number = obj.number; }
~JackTang() {}
void consumer() {
for (int i = 0; i < 10000; i++) {
m_utex2.lock();
m_utex1.lock();
if (!m_que.empty()) {
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor() {
for (int i = 1; i < 10000; i++) {
m_utex1.lock();
m_utex2.lock();
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
int main() {
JackTang j(1);
std::thread t1(&JackTang::consumer, &j);
std::thread t2(&JackTang::productor, &j);
t1.detach();
t2.detach();
return 0;
}
运行结果:
取出元素:255691592
free(): invalid pointer
thread_learn: ../nptl/pthread_mutex_lock.c:117: __pthread_mutex_lock: Assertion `mutex->__data.__owner == 0' failed.
Aborted (core dumped)
更改:
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
class JackTang {
public:
int number;
JackTang(int a) : number(a) {}
JackTang(const JackTang &obj) { number = obj.number; }
~JackTang() {}
void consumer() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
if (m_que.empty()) {
cout << "m_que队列为空" << endl;
} else {
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
int main() {
JackTang j(1);
std::thread t2(&JackTang::productor, &j);
std::thread t1(&JackTang::consumer, &j);
t1.detach();
t2.detach();
return 0;
}
运行结果:
--------
Segmentation fault (core dumped)
--------
Segmentation fault (core dumped)
--------
取出元素:322800456
取出元素:-402639669
取出元素:-125994
取出元素:1866304328
取出元素:-402639669
取出元素:-126006
取出元素:859671368
取出元素:-402639672
取出元素:-121714
Segmentation fault (core dumped)
分析原因:detach分离后,main函数运行太快。再改,降低main函数运行速度:
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
class JackTang {
public:
int number;
JackTang(int a) : number(a) {}
JackTang(const JackTang &obj) { number = obj.number; }
~JackTang() {}
void consumer() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
if (m_que.empty()) {
cout << "m_que队列为空" << endl;
} else {
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
int main() {
JackTang j(1);
std::thread t2(&JackTang::productor, &j);
std::thread t1(&JackTang::consumer, &j);
t1.detach();
t2.detach();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return 0;
}
运行结果:
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:0
取出元素:1
取出元素:2
取出元素:3
取出元素:4
取出元素:5
取出元素:6
取出元素:7
取出元素:8
取出元素:9
--------
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:0
取出元素:1
取出元素:2
取出元素:3
取出元素:4
取出元素:5
取出元素:6
取出元素:7
取出元素:8
取出元素:9
--------
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
如果把consumer放前面:
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
class JackTang {
public:
int number;
JackTang(int a) : number(a) {}
JackTang(const JackTang &obj) { number = obj.number; }
~JackTang() {}
void consumer() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
if (m_que.empty()) {
cout << "m_que队列为空" << endl;
} else {
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor() {
for (int i = 0; i < 10; i++) {
m_utex1.lock();
m_utex2.lock();
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
int main() {
JackTang j(1);
std::thread t1(&JackTang::consumer, &j);
std::thread t2(&JackTang::productor, &j);
t1.detach();
t2.detach();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return 0;
}
运行结果:
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
--------
插入元素:0
插入元素:1
插入元素:2
取出元素:0
取出元素:1
插入元素:3
插入元素:4
插入元素:5
取出元素:2
取出元素:3
取出元素:4
取出元素:5
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:6
--------
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
--------
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:0
取出元素:1
取出元素:2
取出元素:3
取出元素:4
5.3.3 std::lock()函数模板
- std::lock(mutex1,mutex2……); 一次锁定多个互斥锁(一般这种情况很少),用于处理多个互斥锁。
- 如果互斥锁中一个没锁住,它就等着,等所有互斥锁都锁住,才能继续执行。如果有一个没锁住,就会把已经锁住的释放掉(要么互斥锁都锁住,要么都没锁住,防止死锁)
#include <chrono>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
using namespace std;
class JackTang {
public:
int number;
JackTang(int a) : number(a) {}
JackTang(const JackTang &obj) { number = obj.number; }
~JackTang() {}
void consumer() {
for (int i = 0; i < 10; i++) {
std::lock(m_utex1, m_utex2);
if (m_que.empty()) {
cout << "m_que队列为空" << endl;
} else {
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor() {
for (int i = 0; i < 10; i++) {
std::lock(m_utex1, m_utex2);
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
int main() {
JackTang j(1);
std::thread t1(&JackTang::consumer, &j);
std::thread t2(&JackTang::productor, &j);
t1.detach();
t2.detach();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return 0;
}
运行结果:
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
--------
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:0
取出元素:1
取出元素:2
取出元素:3
取出元素:4
取出元素:5
取出元素:6
取出元素:7
取出元素:8
取出元素:9
--------
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
取出元素:0
取出元素:1
取出元素:2
取出元素:3
取出元素:4
取出元素:5
取出元素:6
取出元素:7
取出元素:8
取出元素:9
--------
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
m_que队列为空
插入元素:0
插入元素:1
插入元素:2
插入元素:3
插入元素:4
插入元素:5
插入元素:6
插入元素:7
插入元素:8
插入元素:9
六.unique_lock(类模板)详解
6.1.unique_lock取代lock_guard
- unique_lock比lock_guard灵活很多(多出来很多用法),效率差一点。
unique_lock<mutex> myUniLock(myMutex);
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
using namespace std;
list<int> test_list;
mutex my_mutex;
void in_list() {
for (int num = 0; num < 10; num++) {
std::unique_lock<std::mutex> my_guard(my_mutex);
cout << "插入数据: " << num << endl;
test_list.push_back(num);
}
}
void out_list() {
for (int num = 0; num < 10; ++num) {
std::unique_lock<std::mutex> my_guard(my_mutex);
if (!test_list.empty()) {
int tmp = test_list.front();
test_list.pop_front();
cout << "取出数据:" << tmp << endl;
} else {
cout << "list数组为空" << endl;
}
}
}
int main() {
thread in_thread(in_list);
thread out_thread(out_list);
in_thread.join();
out_thread.join();
cout << "Hello World!" << endl;
return 0;
}
运行结果:
插入数据: 0
插入数据: 1
插入数据: 2
插入数据: 3
插入数据: 4
插入数据: 5
插入数据: 6
插入数据: 7
插入数据: 8
插入数据: 9
取出数据:0
取出数据:1
取出数据:2
取出数据:3
取出数据:4
取出数据:5
取出数据:6
取出数据:7
取出数据:8
取出数据:9
Hello World!
--------
插入数据: 0
插入数据: 1
插入数据: 2
插入数据: 3
插入数据: 4
插入数据: 5
插入数据: 6
插入数据: 7
插入数据: 8
插入数据: 9
取出数据:0
取出数据:1
取出数据:2
取出数据:3
取出数据:4
取出数据:5
取出数据:6
取出数据:7
取出数据:8
取出数据:9
Hello World!
--------
插入数据: 0
插入数据: 1
插入数据: 2
插入数据: 3
插入数据: 4
插入数据: 5
插入数据: 6
插入数据: 7
插入数据: 8
插入数据: 9
取出数据:0
取出数据:1
取出数据:2
取出数据:3
取出数据:4
取出数据:5
取出数据:6
取出数据:7
取出数据:8
取出数据:9
Hello World!
--------
插入数据: 0
插入数据: 1
插入数据: 2
插入数据: 3
插入数据: 4
插入数据: 5
插入数据: 6
插入数据: 7
取出数据:0
取出数据:1
取出数据:2
取出数据:3
取出数据:4
取出数据:5
取出数据:6
取出数据:7
list数组为空
list数组为空
插入数据: 8
插入数据: 9
Hello World!
6.2.unique_lock的第二个参数
6.2.1 std::adopt_lock:
- 表示这个互斥锁已经被lock(),即不需要在构造函数中lock这个互斥锁了。
- 前提:必须提前lock
- lock_guard中也可以用这个参数
m_mutex.lock();
unique_lock<mutex> m_unique(m_mutex,adopt_lock);
、、、
、、、
6.2.2 std::try_to_lock:
- 尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里;
- 使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方
- 前提:不能提前lock();
- owns_locks()方法判断是否拿到锁,如拿到返回true
unique_lock<mutex> m_unique(m_mutex,try_to_lock);
if (m_unique.owns_lock()) {
printf("拿到锁,容器插入数据\n", );
} else {
printf("没有拿到锁,暂时不能插入数据\n" );
}
#include <iostream>
#include <list>
#include <mutex>
#include <thread>
using namespace std;
list<int> test_list;
mutex my_mutex;
void in_list() {
for (int num = 0; num < 10000; num++) {
std::unique_lock<std::mutex> my_unique(my_mutex, std::try_to_lock);
if (my_unique.owns_lock()) {
cout << "插入数据: " << num << endl;
test_list.push_back(num);
} else {
cout << "没能拿到锁,只能干点别的事" << endl;
}
}
}
void out_list() {
for (int num = 0; num < 10000; ++num) {
std::unique_lock<std::mutex> my_unique(my_mutex);
std::chrono::seconds dura(1);
std::this_thread::sleep_for(dura);
if (!test_list.empty()) {
int tmp = test_list.front();
test_list.pop_front();
cout << "取出数据:" << tmp << endl;
} else {
cout << "list数组为空" << endl;
}
}
}
int main() {
thread in_thread(in_list);
thread out_thread(out_list);
in_thread.join();
out_thread.join();
cout << "Hello World!" << endl;
return 0;
}
这个例子需要更多的数据观察,不贴结果了。
6.2.3 std::defer_lock:
- 如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
- 不给它加锁的目的是以后可以调用unique_lock的一些方法
- 前提:不能提前lock
6.3.unique_lock的成员函数(前三个与std::defer_lock联合使用)
unique_lock的成员函数
- lock(),加锁
- unlock(),解锁;
- try_lock(),尝试给互斥锁加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数不阻塞的;
- release(),返回它所管理的lutext对象指针,并释放所有权﹔也就是说,这个unique_lock和mutex不再有关系。
6.3.1 lock():加锁。
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
不用自己unlock();
6.3.2 unlock():解锁。
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
myUniLock.unlock();
myUniLock.lock();
因为一些非共享代码要处理,可以暂时先unlock(),用其他线程把它们处理了,处理完后再lock()。
6.3.3 try_lock():尝试给互斥锁加锁
如果拿不到锁,返回false,否则返回true。
6.3.4 release():
- unique_lock
myUniLock(myMutex);相当于把myMutex和myUniLock绑定在了一起,release()就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权 - mutex* ptx =
myUniLock.release();所有权由ptx接管,如果原来mutex对象处理加锁状态,就需要ptx在以后进行解锁了。
lock的代码段越少,执行越快,整个程序的运行效率越高。
- a.锁住的代码少,叫做粒度细,执行效率高;
- b.锁住的代码多,叫做粒度粗,执行效率低;
6.4.unique_lock所有权的传递
unique_lock<mutex> myUniLock(myMutex) ;把myMutex和myUniLock绑定在了一起,也就是myUniLock拥有myMutex的所有权
6.4.1. 使用move转移
- myUniLock拥有myMutex的所有权,myUniLock可以把自己对myMutex的所有权转移,但是不能复制。
- unique_lock myUniLock2(std::move(myUniLock));
现在myUniLock2拥有myMutex的所有权。
6.4.2. 在函数中return一个临时变量,即可以实现转移
unique_lock<mutex> aFunction() {
unique_lock<mutex> myUniLock(myMutex);
return myUniLock;
}
std::unique_lock<std::mutex> sbguard = aFunction();
七.单例设计模式共享数据分析、解决,call_once
7.1.设计模式
略
7.2.单例设计模式
- 整个项目中,有某个或者某些特殊的类,只能创建一个属于该类的对象。
- 单例类:只能生成一个对象。
7.3.单例设计模式共享数据分析、解决
- 面临问题:需要在自己创建的线程中来创建单例类的对象,这种线程可能不止一个。可能面临GetInstance()这种成员函数需要互斥。
- 可以在加锁前判断m_instance是否为空,否则每次调用Singleton::getInstance()都要加锁,十分影响效率。
这里讲得比较空洞,可以参考这篇博客:深入探索单例设计模式:以百度 Apollo 为例,3.2 多线程安全的懒汉式单例:单检锁实现 小节介绍了多线程加锁。
#include <iostream>
#include <mutex>
using namespace std;
class HungrySingleton {
private:
static HungrySingleton* pinstance_;
private:
HungrySingleton() {}
HungrySingleton(const HungrySingleton&) = delete;
HungrySingleton& operator=(const HungrySingleton&) = delete;
public:
~HungrySingleton() {}
public:
static HungrySingleton* GetInstance();
};
HungrySingleton* HungrySingleton::pinstance_ = new HungrySingleton;
HungrySingleton* HungrySingleton::GetInstance() { return pinstance_; }
class LazySingleton {
private:
static LazySingleton* pinstance_;
static std::mutex mutex_;
private:
LazySingleton() {}
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
~LazySingleton() {}
public:
static LazySingleton* GetInstance();
};
LazySingleton* LazySingleton::pinstance_{nullptr};
std::mutex LazySingleton::mutex_;
LazySingleton* LazySingleton::GetInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (nullptr == pinstance_) {
pinstance_ = new LazySingleton;
}
return pinstance_;
}
class LazySingleton2 {
private:
static LazySingleton2* pinstance_;
static std::mutex mutex_;
private:
LazySingleton2() {}
LazySingleton2(const LazySingleton2&) = delete;
LazySingleton2& operator=(const LazySingleton2&) = delete;
public:
~LazySingleton2() {}
public:
static LazySingleton2* GetInstance();
};
LazySingleton2* LazySingleton2::pinstance_{nullptr};
std::mutex LazySingleton2::mutex_;
LazySingleton2* LazySingleton2::GetInstance() {
if (nullptr == pinstance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (nullptr == pinstance_) {
pinstance_ = new LazySingleton2;
}
}
return pinstance_;
}
int main(void) {
LazySingleton2* singer = LazySingleton2::GetInstance();
LazySingleton2* singer2 = LazySingleton2::GetInstance();
if (singer == singer2)
cout << "二者是同一个实例" << endl;
else
cout << "二者不是同一个实例" << endl;
cout << "---- 以下是饿汉式 ----" << endl;
HungrySingleton* singer3 = HungrySingleton::GetInstance();
HungrySingleton* singer4 = HungrySingleton::GetInstance();
if (singer3 == singer4)
cout << "二者是同一个实例" << endl;
else
cout << "二者不是同一个实例" << endl;
return 0;
}
如果觉得在单例模式new了一个对象,而没有自己delete掉,这样不合理。可以增加一个类中类CGarhuishou,new一个单例类时创建一个静态的CGarhuishou对象,这样在程序结束时会调用CGarhuishou的析构函数,释放掉new出来的单例对象。
#include <iostream>
#include <mutex>
using namespace std;
class LazySingleton2 {
private:
static LazySingleton2* pinstance_;
static std::mutex mutex_;
private:
LazySingleton2() {}
LazySingleton2(const LazySingleton2&) = delete;
LazySingleton2& operator=(const LazySingleton2&) = delete;
public:
~LazySingleton2() {}
public:
static LazySingleton2* GetInstance();
class CGarhuishou {
public:
~CGarhuishou() {
if (LazySingleton2::pinstance_) {
delete LazySingleton2::pinstance_;
LazySingleton2::pinstance_ = NULL;
}
}
};
};
LazySingleton2* LazySingleton2::pinstance_{nullptr};
std::mutex LazySingleton2::mutex_;
LazySingleton2* LazySingleton2::GetInstance() {
if (nullptr == pinstance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (nullptr == pinstance_) {
static CGarhuishou huishou;
pinstance_ = new LazySingleton2;
}
}
return pinstance_;
}
int main(void) {
LazySingleton2* singer = LazySingleton2::GetInstance();
LazySingleton2* singer2 = LazySingleton2::GetInstance();
if (singer == singer2)
cout << "二者是同一个实例" << endl;
else
cout << "二者不是同一个实例" << endl;
return 0;
}
- 参考博客中还提到了
使用原子变量确保多线程安全性 ,后续再看。
7.4.std::once_flag & std::call_once()
-
std::call_once() 函数模板,该函数的第一个参数为标记,第二个参数是一个函数名(如a())。 -
功能:能够保证函数a()只被调用一次。具备互斥锁的能力,而且比互斥锁消耗的资源更少,更高效。 -
call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。 -
多个线程同时执行时,一个线程会等待另一个线程先执行。
#include <iostream>
#include <mutex>
using namespace std;
std::once_flag g_flag;
class Singleton {
public:
static void CreateInstance() { instance = new Singleton; }
static Singleton* getInstance() {
std::call_once(g_flag, CreateInstance);
return instance;
}
private:
Singleton() {}
static Singleton* instance;
};
Singleton* Singleton::instance = NULL;
int main(void) {
Singleton* singer = Singleton::getInstance();
Singleton* singer2 = Singleton::getInstance();
if (singer == singer2)
cout << "二者是同一个实例" << endl;
else
cout << "二者不是同一个实例" << endl;
return 0;
}
apollo中的单例模式:
#include <iostream>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
#include <thread>
#define DEFINE_TYPE_TRAIT(name, func) \
template <typename T> \
struct name { \
template <typename Class> \
static constexpr bool Test(decltype(&Class::func) *) { \
return true; \
} \
template <typename> \
static constexpr bool Test(...) { \
return false; \
} \
\
static constexpr bool value = Test<T>(nullptr); \
}; \
\
template <typename T> \
constexpr bool name<T>::value;
DEFINE_TYPE_TRAIT(HasShutdown, Shutdown)
template <typename T>
typename std::enable_if<HasShutdown<T>::value>::type CallShutdown(T *instance) {
instance->Shutdown();
}
template <typename T>
typename std::enable_if<!HasShutdown<T>::value>::type CallShutdown(
T *instance) {
(void)instance;
}
#undef UNUSED
#undef DISALLOW_COPY_AND_ASSIGN
#define UNUSED(param) (void)param
#define DISALLOW_COPY_AND_ASSIGN(classname) \
classname(const classname &) = delete; \
classname &operator=(const classname &) = delete;
#define DECLARE_SINGLETON(classname) \
public: \
static classname *Instance(bool create_if_needed = true) { \
\
static classname *instance = nullptr; \
if (!instance && create_if_needed) { \
\
static std::once_flag flag; \
std::call_once(flag, \
[&] { instance = new (std::nothrow) classname(); }); \
} \
return instance; \
} \
\
static void CleanUp() { \
auto instance = Instance(false); \
if (instance != nullptr) { \
CallShutdown(instance); \
} \
} \
\
private: \
classname(); \
\
DISALLOW_COPY_AND_ASSIGN(classname)
class SingletonA {
private:
~SingletonA() = default;
private:
static int num;
public:
static void GetNum() {
std::cout << "\n number of instances of SingletonA: " << num << std::endl;
}
DECLARE_SINGLETON(SingletonA)
};
int SingletonA::num = 0;
SingletonA::SingletonA() { ++num; }
class SingletonB {
private:
~SingletonB() = default;
private:
static int num;
public:
void Shutdown();
static void GetNum() {
std::cout << "\n number of instances of SingletonB: " << num << std::endl;
}
DECLARE_SINGLETON(SingletonB)
};
int SingletonB::num = 0;
SingletonB::SingletonB() { ++num; }
void SingletonB::Shutdown() {
auto instance = Instance(false);
if (instance != nullptr) {
delete instance;
num = 0;
}
std::cout << "\n SingletonB::Shutdown method was called." << std::endl;
}
template <typename T>
void ThreadFunc() {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
T *p = T::Instance();
}
int main() {
std::thread tA1(ThreadFunc<SingletonA>);
std::thread tA2(ThreadFunc<SingletonA>);
std::thread tB1(ThreadFunc<SingletonB>);
std::thread tB2(ThreadFunc<SingletonB>);
tA1.join();
tA2.join();
tB1.join();
tB2.join();
SingletonA::GetNum();
SingletonB::GetNum();
SingletonA::CleanUp();
SingletonB::CleanUp();
SingletonA::GetNum();
SingletonB::GetNum();
return 0;
}
运行结果:
number of instances of SingletonA: 1
number of instances of SingletonB: 1
SingletonB::Shutdown method was called.
number of instances of SingletonA: 1
number of instances of SingletonB: 0
八.condition_variable、wait、notify_one、notify_all
8.1.条件变量condition_variable、wait、notify_one、notify_all
std::condition_variable实际上是一个类,是一个和条件相关的类,说白了就是等待一个条件达成。wait为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;
});
-
wait()用来等一个东西 -
如果第二个参数的lambda表达式返回值是true,那么wait()直接返回并继续执行。 -
如果第二个参数的lambda表达式返回值是false,那么wait()将解锁互斥锁,并阻塞到本行
- 阻塞到什么时候为止呢?阻塞到其他某个线程调用notify_one()成员函数为止;
-
如果没有第二个参数,那么效果跟第二个参数lambda表达式返回false效果一样
- wait()将解锁互斥锁,并阻塞到本行,阻塞到其他某个线程调用notify_one()成员函数为止。
-
当其他线程用notify_one() 将本wait(原来是睡着/阻塞)的状态唤醒后,wait就开始恢复干活了,恢复后wait干什么活?
- a) wait() 不断的尝试重新获取互斥锁锁,如果获取不到,那么流程就卡在wait这里等着获取,如果获取到了,那么wait就走下来。
- b1)如果wait有第二个参数(lambda),就判断这个lambda表达式,如果lambda表达式为false,那wait又对互斥锁解锁,然后又休眠这里等待再次被notify_one唤醒。
- b2) 如果lambda表达式为true,则wait返回,流程走下来。(此时互斥锁被锁着)
- b3) 如果wait没有第二个参数,则wait返回,流程走下来。
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <list>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
using namespace std;
class A {
public:
void inMsgRecvQueue() {
for (int i = 1; i < 10; ++i) {
cout << "inMsgRecvQueue执行了,插入一个元素" << i << endl;
std::unique_lock<mutex> in_mutex_guard(my_mutex);
msgRecvQueue.push_back(i);
cond_var.notify_one();
}
}
void outMsgRecvQueue() {
int command{};
while (true) {
std::unique_lock<mutex> outMutex(my_mutex);
cond_var.wait(outMutex, [this]() {
if (!msgRecvQueue.empty()) return true;
return false;
});
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
outMutex.unlock();
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex;
std::condition_variable cond_var;
};
int main() {
A obja;
std::thread outMsgThread(&A::outMsgRecvQueue, &obja);
std::thread inMsgThread(&A::inMsgRecvQueue, &obja);
inMsgThread.detach();
outMsgThread.detach();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::cout << "主线程结束" << std::endl;
return 0;
}
运行结果:
inMsgRecvQueue执行了,插入一个元素2
inMsgRecvQueue执行了,插入一个元素3
inMsgRecvQueue执行了,插入一个元素4
inMsgRecvQueue执行了,插入一个元素5
inMsgRecvQueue执行了,插入一个元素6
inMsgRecvQueue执行了,插入一个元素7
inMsgRecvQueue执行了,插入一个元素8
inMsgRecvQueue执行了,插入一个元素9
outMsgRecvQueue()执行,取出一个元素1
outMsgRecvQueue()执行,取出一个元素2
outMsgRecvQueue()执行,取出一个元素3
outMsgRecvQueue()执行,取出一个元素4
outMsgRecvQueue()执行,取出一个元素5
outMsgRecvQueue()执行,取出一个元素6
outMsgRecvQueue()执行,取出一个元素7
outMsgRecvQueue()执行,取出一个元素8
outMsgRecvQueue()执行,取出一个元素9
主线程结束
- wait()不断尝试获取互斥锁锁,如果获取不到那么流程就卡在wait()这里等待获取,如果获取到了,那么wait()就继续执行,获取到了锁
1、如果wait有第二个参数就判断这个lambda表达式。
- a)如果表达式为false,那wait又对互斥锁解锁,然后又休眠,等待再次被notify_one()唤醒
- b)如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥锁已被锁住)。
2、如果wait没有第二个参数,则wait返回,流程走下去。
流程只要走到了wait()下面则互斥锁一定被锁住了。
8.2.深入思考
上面的代码可能导致出现一种情况:
因为outMsgRecvQueue()与inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在msgRecvQueue 中已经有了很多消息,但是,outMsgRecvQueue还是被唤醒一次只处理一条数据。这时可以考虑把outMsgRecvQueue多执行几次,或者对inMsgRecvQueue进行限流。
8.3.notify_all()
九.async、future、packaged_task、promise
本节内容需要包含头文件#include <future>
9.1.std::async、std::future创建后台任务并返回值
-
std::async是一个函数模板,用来启动一个异步任务,启动一个异步任务之后,它返回一个std::future对象,这个对象是个类模板。 -
什么叫“启动一个异步任务”?就是自动创建一个线程,并开始 执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果,我们可以通过调用future对象的成员函数get()来获取结果。 -
“future”将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到。 -
std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。 -
std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。
std::async可以看成是 std::threads 的一个进阶,
假设我们要执行另外一个函数work:
bool work ( int x);
使用 std::async 调用 work(123) 函数的方法为:
std::future< bool > fut = std::async (wrok, 123 );
调用 std::async 之后会立即传回一个 std::future 的结果ret,随后即可通过这个结果取得计算结果:
bool r = fut.get();
这就是 std::async 最简单的用法。
例子,检查一个整数是否为质数:
#include <future>
#include <iostream>
bool is_prime(int x) {
std::cout << "Calculating. Please, wait... \n";
bool r = true;
for (int i = 2; i < x; ++i) {
if (x % i == 0) {
r = false;
break;
}
}
std::cout << "We're done. \n";
return r;
}
void sleep(int milisec) {
std::this_thread::sleep_for(std::chrono::milliseconds(milisec));
}
int main() {
std::future<bool> fut = std::async(is_prime, 334214467);
sleep(200);
std::cout << "Do something in main thread ... \n";
sleep(200);
std::cout << "Do something in main thread ... \n";
sleep(200);
std::cout << "Do something in main thread ... \n";
bool r = fut.get();
if (r) {
std::cout << "It is prime! \n";
} else {
std::cout << "It is not prime. \n";
}
return 0;
}
运行结果:
Calculating. Please, wait...
Do something in main thread ...
Do something in main thread ...
Do something in main thread ...
We're done.
It is prime!
例子:
#include <future>
#include <iostream>
using namespace std;
class A {
public:
int mythread(int mypar) {
cout << mypar << endl;
return mypar;
}
};
int mythread() {
cout << "mythread() start"
<< " thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< " thread id = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
A a;
int tmp = 12;
cout << "main"
<< " thread id = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
std::future<int> result2 = std::async(&A::mythread, &a, tmp);
cout << result2.get() << endl;
cout << "good luck" << endl;
return 0;
}
运行结果:
main thread id = 140627844208448
continue........
mythread() start thread id = 140627802838784
mythread() end thread id = 140627802838784
5
12
12
good luck
我们通过向std::async()传递一个参数,该参数是std::launch类型(枚举类型),来达到一些特殊的目的:
9.1.1.std::async 的两种执行模式之std::launch::async
- std::launch::async并行执行
这种方式就是另外开启一个线程,以平行的方式执行,这个应该是大部分人所期望的模式。
#include <future>
#include <iostream>
using namespace std;
int mythread() {
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::async, mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
cout << "good luck" << endl;
return 0;
}
运行结果:
mainthreadid = 140706613393216
continue........
mythread() startthreadid = 140706572023552
mythread() endthreadid = 140706572023552
5
good luck
9.1.2.std::async 的两种执行模式之std::launch::deferred
- std::launch::deferred延后执行
这种方式就是很单纯的将函数调用的时间延后,当 std::future 调用 get() 时,才去真正执行其中的函数,如果wait()或者get()没有被调用,则不会执行,而执行的方式就是一般的函数调用,没有任何平行化效果。
#include <future>
#include <iostream>
using namespace std;
int mythread() {
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::deferred, mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
cout << "good luck" << endl;
return 0;
}
运行结果:
mainthreadid = 140539030345536
continue........
mythread() startthreadid = 140539030345536
mythread() endthreadid = 140539030345536
5
good luck
永远都会先打印出continue…,然后才会打印出mythread() start和mythread() end等信息。
9.2.std::packaged_task:打包任务,把任务包装起来
类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int mythread(int mypar) {
cout << mypar << endl;
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
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;
return 0;
}
运行结果:
mainthreadid = 139888845096768
1
mythread() startthreadid = 139888803727104
mythread() endthreadid = 139888803727104
5
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int mythread(int mypar) {
cout << mypar << endl;
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
cout << "good luck" << endl;
return 0;
}
packaged_task包装起来的可调用对象还可以直接调用,从这个角度来讲,packaged_task对象也是一个可调用对象
运行结果:
mainthreadid = 140083709134656
1
mythread() startthreadid = 140083667764992
mythread() endthreadid = 140083667764992
5
good luck
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int mythread(int mypar) {
cout << mypar << endl;
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
mypt(1);
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
}
运行结果:
mainthreadid = 139697186420544
1
mythread() startthreadid = 139697186420544
mythread() endthreadid = 139697186420544
5
- std::promise,类模板
我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来
#include <future>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void mythread(std::promise<int> &tmp, int clac) {
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
int result = clac;
tmp.set_value(result);
return;
}
vector<std::packaged_task<int(int)>> task_vec;
int main() {
std::promise<int> myprom;
std::thread t1(mythread, std::ref(myprom), 180);
t1.join();
std::future<int> fu1 =
myprom.get_future();
auto result = fu1.get();
cout << "result = " << result << endl;
}
运行结果:
mythread() startthreadid = 140174314473216
mythread() endthreadid = 140174314473216
result = 180
十.future其他成员函数、shared_future、atomic
future成员函数:
-
获取结果
-
状态
- valid : 检查 future 是否拥有共享状态
- wait : 等待结果变得可用
- wait_for : 等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。
- wait_until : 等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。
10.1.std::future 的成员函数
- 1、std::future_status status = result.wait_for(std::chrono::seconds(几秒));
- 卡住当前流程,等待std::async()的异步任务运行一段时间,然后返回其状态std::future_status。如果std::async()的参数是 std::launch::deferred(延迟执行),则不会卡住主流程。
- std::future_status是枚举类型,表示异步任务的执行状态。类型的取值有
- std::future_status::timeout 共享状态在经过指定的等待时间内仍未就绪
- std::future_status::ready 共享状态就绪
- std::future_status::deferred 共享状态持有的函数正在延迟运行,结果将仅在显式请求时计算
wait_for使用例子
#include <future>
#include <iostream>
using namespace std;
int mythread() {
cout << "mythread() start"
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end"
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main"
<< "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread);
cout << "continue........" << endl;
std::future_status status = result.wait_for(std::chrono::seconds(1));
if (status == std::future_status::timeout) {
cout << "超时了,线程还没有执行完" << endl;
}
return 0;
}
运行结果:
mainthreadid = 140610447968064
continue........
mythread() startthreadid = 140610406598400
超时了,线程还没有执行完
mythread() endthreadid = 140610406598400
#include <future>
#include <iostream>
using namespace std;
int mythread() {
cout << "mythread() start "
<< "threadid = " << std::this_thread::get_id() << endl;
cout << "mythread() end "
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main "
<< "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread);
cout << "continue........" << endl;
std::future_status status = result.wait_for(std::chrono::seconds(6));
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;
}
cout << "good luck" << endl;
return 0;
}
运行结果:
main threadid = 140209946228544
continue........
mythread() start threadid = 140209904858880
mythread() end threadid = 140209904858880
线程执行成功,返回
5
good luck
get()只能使用一次,比如如果
auto a = result.get();
cout << result.get() << endl;
就会报告异常 因为get()函数的设计是一个移动语义,相当于将result中的值移动到了a中,再次get就报告了异常。
- std::future的 get() 成员函数是转移数据
- std::shared_future 的 get()成员函数是复制数据
#include <future>
#include <iostream>
#include <thread>
using namespace std;
int mythread() {
cout << "mythread() start "
<< "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end "
<< "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main "
<< "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int()> mypt(mythread);
std::thread t1(std::ref(mypt));
std::future<int> result = mypt.get_future();
bool ifcanget = result.valid();
std::shared_future<int> result_s(
result.share());
t1.join();
auto myresult1 = result_s.get();
auto myresult2 = result_s.get();
cout << "good luck" << endl;
return 0;
}
运行结果:
main threadid = 140029196932928
mythread() start threadid = 140029155563264
mythread() end threadid = 140029155563264
good luck
10.3.std::atomic原子操作
10.3.1 原子操作概念引出范例:
-
互斥锁:多线程编程中 用于保护共享数据:先锁住, 操作共享数据, 解锁。 -
有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。 -
即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(一条C语句会被拆成3、4条汇编语句来执行,所以仍然有可能混乱)
#include <iostream>
#include <thread>
using namespace std;
int g_count = 0;
void mythread1() {
for (int i = 0; i < 1000000; i++) {
g_count++;
}
}
int main() {
std::thread t1(mythread1);
std::thread t2(mythread1);
t1.join();
t2.join();
cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}
我这里测试的这个用例结果是正确的,没有混乱:
正常情况下结果应该是200 00000次,实际是20000000
使用mutex解决这个问题
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
int g_count = 0;
std::mutex mymutex;
void mythread1() {
for (int i = 0; i < 1000000; i++) {
std::unique_lock<std::mutex> u1(mymutex);
g_count++;
}
}
int main() {
std::thread t1(mythread1);
std::thread t2(mythread1);
t1.join();
t2.join();
cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}
10.3.2 基本的std::atomic用法范例
-
大家可以把原子操作理解成一种:不需要用到互斥锁加锁(无锁)技术的多线程并发编程方式。 -
原子操作:在多线程中不会被打断的程序执行片段。 -
从效率上来说,原子操作要比互斥锁的方式效率要高。 -
互斥锁的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。 -
原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态。 -
std::atomic来代表原子操作,是个类模板。其实std::atomic是用来封装某个类型的值的 -
需要添加#include <atomic> 头文件
范例:
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
std::atomic<int> g_count(0);
void mythread1() {
for (int i = 0; i < 1000000; i++) {
g_count++;
}
}
int main() {
std::thread t1(mythread1);
std::thread t2(mythread1);
t1.join();
t2.join();
cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
std::atomic<bool> g_ifEnd(false);
void mythread() {
std::chrono::milliseconds dura(1000);
while (g_ifEnd == false) {
cout << "thread id = " << std::this_thread::get_id() << "运行中" << endl;
std::this_thread::sleep_for(dura);
}
cout << "thread id = " << std::this_thread::get_id() << "运行结束" << endl;
}
int main() {
std::thread t1(mythread);
std::thread t2(mythread);
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
g_ifEnd = true;
cout << "程序执行完毕" << endl;
t1.join();
t2.join();
}
运行结果:
thread id = 139752726234880运行中
thread id = 139752734627584运行中
thread id = thread id = 139752734627584运行中139752726234880
运行中
thread id = thread id = 139752734627584运行中139752726234880
运行中
thread id = thread id = 139752734627584139752726234880运行中运行中
thread id = thread id = 139752726234880139752734627584运行中运行中
程序执行完毕
thread id = 139752726234880运行结束
thread id = 139752734627584运行结束
总结:
11.std::atomic续谈、std::async深入谈
11.1.std::atomic续谈
#include <atomic>
#include <iostream>
#include <thread>
using namespace std;
std::atomic<int> g_count(0);
void mythread1() {
for (int i = 0; i < 1000000; i++) {
g_count = g_count + 1;
}
}
int main() {
std::thread t1(mythread1);
std::thread t2(mythread1);
t1.join();
t2.join();
cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
}
运行结果:
------------
正常情况下结果应该是200 0000次,实际是1463947
------------
正常情况下结果应该是200 0000次,实际是1030510
------------
正常情况下结果应该是200 0000次,实际是1166023
一般atomic原子操作,针对++,–,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。
11.2.std::async深入理解
11.2.1 std::async参数详述,async 用来创建一个异步任务
-
延迟调用参数 std::launch::deferred【延迟调用】,std::launch::async【强制创建一个线程】 -
std::async()我们一般不叫创建线程(他能够创建线程),我们一般叫它创建一个异步任务。 -
std::async和std::thread最明显的不同,就是 async 有时候并不创建新线程。
-
①如果用std::launch::deferred 来调用async?
- 延迟到调用 get() 或者 wait() 时执行,如果不调用就不会执行
-
②如果用std::launch::async来调用async?
- 强制这个异步任务在新线程上执行,这意味着,系统必须要创建出新线程来运行入口函数。
-
③如果同时用 std::launch::async | std::launch::deferred
- 这里这个 | 意味着async的行为可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案
-
④不带额外参数 std::async(mythread),只给async 一个入口函数名,此时的系统给的默认值是 std::launch::async | std::launch::deferred 和 ③ 一样,有系统自行决定异步还是同步运行。
11.2.2 std::async和std::thread()区别:
- std::thread()如果系统资源紧张可能出现创建线程失败的情况,如果创建线程失败那么程序就可能崩溃,而且不容易拿到函数返回值(不是拿不到)
- std::async()创建异步任务。可能创建线程也可能不创建线程,并且容易拿到线程入口函数的返回值;
由于系统资源限制:
-
①如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。 -
②如果用std::async,一般就不会报异常,因为如果系统资源紧张,无法创建新线程的时候,async不加额外参数的调用方式就不会创建新线程。而是在后续调用get()请求结果时执行在这个调用get()的线程上。
- 如果你强制async一定要创建新线程就要使用 std::launch::async 标记。承受的代价是,系统资源紧张时可能崩溃。
-
③根据经验,一个程序中线程数量 不宜超过100~200 。
11.2.3 async不确定性问题的解决
std::future_status status = result.wait_for(std::chrono::seconds(6));
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互斥锁
12.1&2.windows临界区
-
Windows临界区,同一个线程是可以重复进入的,但是进入的次数与离开的次数必须相等。 C++互斥锁则不允许同一个线程重复加锁。 -
windows临界区是在windows编程中的内容,了解一下即可,效果几乎可以等同于c++11的mutex -
包含#include <windows.h> windows中的临界区同mutex一样,可以保护一个代码段。但windows的临界区可以进入多次,离开多次,但是进入的次数与离开的次数必须相等,不会引起程序报异常出错。
略。
12.3.自动析构技术
- C++:lock_guard防止忘了释放信号量,自动释放
- windows:可以写个类自动释放临界区: 略
12.4.递归独占互斥锁 std::recursive_mutex
12.5.带超时的互斥锁 std::timed_mutex 和 std::recursive_timed_mutex
- 5.1 std::timed_mutex:是待超时的独占互斥锁
try_lock_for(): 等待一段时间,如果拿到了锁,或者超时了未拿到锁,就继续执行(有选择执行)如下:
std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_for(timeout)) {
} else {
std::chrono::milliseconds sleeptime(100);
std::this_thread::sleep_for(sleeptime);
}
try_lock_until(): 参数是一个未来的时间点,在这个未来的时间没到的时间内,如果拿到了锁头,流程就走下来,如果时间到了没拿到锁,流程也可以走下来。
std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_until(chrono::steady_clock::now() + timeout)) {
} else {
std::chrono::milliseconds sleeptime(100);
std::this_thread::sleep_for(sleeptime);
}
两者的区别就是一个参数是时间段,一个参数是时间点
- 5.2 std::recursive_timed_mutex:是待超时的递归独占互斥锁
|