测试用例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_BUILD_TYPE Release )
# 设置编译选项: c++11标准、O3优化、多线程
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 )
并发,线程,进程要求必须掌握
两个或者更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务;
使用并发的原因:主要就是同时可以干多个事,提高性能
磁盘上的一个文件,windows下,扩展名为.exe;linux下,ls -la,rwx(可读可写可执行)
运行一个可执行程序,在windows下,可双击;在linux下,./文件名
进程,一个可执行程序运行起来了,就叫创建了一个进程。进程就是运行起来的可执行程序。
①
a)每个进程(执行起来的可执行程序),都有唯一的一个主线程
b)当执行可执行程序时,产生一个进程后,这个主线程就随着这个进程默默启动起来了
c)ctrl+F5运行这个程序的时候,实际上是进程的主线程来执行(调用)这个main函数中的代码
线程:用来执行代码的。线程东西,可以理解为一条代码的执行通路。
②
a)除了主线程之外,可以通过写代码来创建其他线程,其他线程走的是别的道路,甚至去不同的地方
b)每创建一个新线程,就可以在同一时刻,多干一个不同的事(多走一条不同的代码执行路径)
③
a)多线程(并发)
b)线程并不是越多越好,每个线程,都需要一个独立的堆栈空间(大约1M),线程之间的切换要保存很多中间状态,切换也会耗费本该属于程序运行的时间
c)必须使用多线程的案例
实现并发的手段:
以往
C++11
创建一个线程:
必须要明白:有两个线程在跑,相当于整个程序中有两条线在同时走,即使一条被阻塞,另一条也能运行
#include
#include
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
//(1)创建线程,线程执行起点(入口)是myPrint;(2)执行线程
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
#include
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
// (1)创建线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
// join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完,
// 当myPrint执行完毕,join()就执行完毕,主线程才继续往下执行
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
多次运行结果均为:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完
#include
#include
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
//(1)创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
// join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完,
//当myPrint执行完毕,join()就执行完毕,主线程继续往下执行
// myThread.join();
//(3)传统多线程程序中,主线程要等待子线程执行完毕,然后自己才能向下执行
// detach:分离,主线程不再与子线程汇合,不再等待子线程
// detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
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
#include
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
//(1)创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
// join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完,
// 当myPrint执行完毕,join()就执行完毕,主线程继续往下执行
// myThread.join();
//(3)传统多线程程序中,主线程要等待子线程执行完毕,然后自己才能向下执行
// detach:分离,主线程不再与子线程汇合,不再等待子线程
// detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
// myThread.detach();
//(4)joinable()判断是否可以成功使用join()或者detach()
//如果返回true,证明可以调用join()或者detach()
//如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了
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
#include
using namespace std;
void myPrint() {
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
return;
}
int main() {
//(1)创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(myPrint);
cout << "Hello World-1!" << endl;
// join意为汇合,子线程和主线程汇合,join会阻塞主线程并等待myPrint执行完,
//当myPrint执行完毕,join()就执行完毕,主线程继续往下执行
myThread.join();
//(3)传统多线程程序中,主线程要等待子线程执行完毕,然后自己才能向下执行
// detach:分离,主线程不再与子线程汇合,不再等待子线程
// detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
// myThread.detach();
//(4)joinable()判断是否可以成功使用join()或者detach()
//如果返回true,证明可以调用join()或者detach()
//如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了
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!
重要补充:线程类参数是一个可调用对象。
#include
#include
using namespace std;
class Ta {
public:
void operator()() //不能带参数
{
cout << "我的线程开始运行" << endl;
//-------------
//-------------
cout << "我的线程运行完毕" << endl;
}
};
int main() {
cout << "Hello World-1!" << endl;
Ta ta;
//(1)创建线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(ta);
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
运行结果:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
#include
#include
using namespace std;
auto lambdaThread = [] {
cout << "我的线程开始执行了" << endl;
//-------------
//-------------
cout << "我的线程开始执行了" << endl;
};
int main() {
cout << "Hello World-1!" << endl;
//(1)创建线程,线程执行起点(入口)是myPrint;(2)执行线程
thread myThread(lambdaThread);
myThread.join();
cout << "Hello World-2!" << endl;
return 0;
}
运行结果:
Hello World-1!
我的线程开始运行
我的线程运行完毕
Hello World-2!
#include
#include
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)传值也可以
// 在其他的构造函数中&obj不会代表引用,会被当成取地址
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
// 第二个参数可以传递对象s,也可以传递引用std::ref(s)或&s
// 传递s,会调用拷贝构造函数在子线程中生成一个新的对象
// 传递&,子线程中还是用的原来的对象,所以就不能detach,因为主线程运行完毕会把该对象释放掉
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!
#include
#include
using namespace std;
void myPrint(const int& i, char* pmybuf) {
// 如果线程从主线程detach了
// i不是mvar真正的引用,实际上值传递,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用
// 推荐改为const int i
cout << i << endl;
// pmybuf还是指向原来的字符串,所以这么写是不安全的
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';
// myThread.detach();
cout << "Hello World!" << endl;
}
运行结果:
10
This is a test
Hello World!
为什么是大写,参考:
#include
#include
#include
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";
//如果detach了,这样仍然是不安全的
//因为存在主线程运行完了,mybuf被回收了,系统采用mybuf隐式类型转换成string
//推荐先创建一个临时对象thread myThread(myPrint, mvar,
//string(mybuf));就绝对安全了。。。。
thread myThread(myPrint, mvar, mybuf);
// myThread.join();
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
#include
#include
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";
//如果detach了,这样仍然是不安全的
//因为存在主线程运行完了,mybuf被回收了,系统采用mybuf隐式类型转换成string
// 推荐先创建一个临时对象
thread myThread(myPrint, mvar,string(mybuf));//就绝对安全了。。。。
// thread myThread(myPrint, mvar, mybuf);
// myThread.join();
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
总结
1.如果传递int这种简单类型,推荐使用值传递,不要用引用
2.如果传递类对象,避免使用隐式类型转换,全部都是创建线程这一行就创建出临时对象,然后在函数参数里,用引用来接,否则还会创建出一个对象
3.终极结论:建议不使用detach
线程id概念
id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字都不一样
线程id可以用C++标准库里的函数来获取。std::this_thread::get_id()来获取
#include
#include
#include
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!
#include
#include
using namespace std;
class A {
public:
mutable int m_i; //mutable: m_i即使实在const中也可以被修改
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);
// myPrint(const A& pmybuf)中引用不能去掉,如果去掉会多创建一个对象
// const也不能去掉,去掉会出错
// 即使是传递的const引用,但在子线程中还是会调用拷贝构造函数构造一个新的对象,
// 所以在子线程中修改m_i的值不会影响到主线程
// 如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(myPrint, std::ref(myObj));
// 这样const就是真的引用了,myPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了
thread myThread(myPrint, myObj);
myThread.join();
// myThread.detach();
cout << "Hello World!" << endl;
}
运行结果:
子线程myPrint的参数地址是0x55aac9ce2e78
thread = 140061621405440
Hello World!
#include
#include
#include
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));
//独占式指针只能通过std::move()才可以传递给另一个指针
//传递后up就指向空,新的ptn指向原来的内存
//所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
thread myThread(myPrint, std::move(up));
myThread.join();
// myThread.detach();
return 0;
}
运行结果:
thread = 139634421532416
#include
#include
#include
#include
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执行结束
std::mutex 的成员函数
包含#include
头文件
加锁是让别的线程不能写。
该例来源
// 不加锁
// mutex example
#include // std::cout
#include // std::mutex
#include // std::thread
std::mutex mtx; // mutex for critical section
void print_block(int n, char c) {
// critical section (exclusive access to std::cout signaled by locking mtx):
// 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;
}
运行结果不可控:
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$*$***$****$*$$$$$*$$$******$*$*$$$*$
*
--------
*******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
*******************
--------
**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
--------
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
**************************************************
--------
*******************************$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
*******************
加锁:
// mutex example
#include // std::cout
#include // std::mutex
#include // std::thread
std::mutex mtx; // mutex for critical section
void print_block(int n, char c) {
// critical section (exclusive access to std::cout signaled by locking mtx):
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
#include // std::mutex
#include // std::thread
int g_num = 0; // 为 g_num_mutex 所保护
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
#include // std::mutex
#include // std::thread
int g_num = 0; // 为 g_num_mutex 所保护
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 // std::chrono
#include
#include // std::mutex
#include // std::thread
int g_num = 0; // 为 g_num_mutex 所保护
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
lock_guard类模板可直接取代lock( )、unlack( ),并且使用lock_guard类模板后不能再对这个保护区域使用lock( )、unlack( ). lock_guard类模板超出作用域时会自动析构释放锁。
#include // std::chrono
#include
#include // std::mutex
#include // std::thread
int g_num = 0; // 为 g_num_mutex 所保护
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
std::lock_guard
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
adopt_guard为结构体对象,起一个标记作用,表示这个互斥锁已经lock(),不需要再lock()。
原博文和这篇好像
死锁至少有两个互斥锁mutex1,mutex2。
1、以确定的顺序获得锁
按照上面的例子,两个线程获得锁的时序图如下:
如果此时把获得锁的时序改成:
那么死锁就永远不会发生。
2、超时放弃
该方法可以按照固定时长等待锁,线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
死锁例子:
#include // std::chrono
#include
#include // std::mutex
#include
#include // std::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);
// 类中成员函数作为线程入口, 参考 2.2.其他创建线程的方法
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
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 // std::chrono
#include
#include // std::mutex
#include
#include // std::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);
// 类中成员函数作为线程入口, 参考 2.2.其他创建线程的方法
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
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 // std::chrono
#include
#include // std::mutex
#include
#include // std::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);
// 类中成员函数作为线程入口, 参考 2.2.其他创建线程的方法
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
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 // std::chrono
#include
#include // std::mutex
#include
#include // std::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);
// 类中成员函数作为线程入口, 参考 2.2.其他创建线程的方法
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
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
#include // std::chrono
#include
#include // std::mutex
#include
#include // std::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);
// 类中成员函数作为线程入口, 参考 2.2.其他创建线程的方法
// 调用方式:对象成员函数地址,类实例,[成员函数参数]
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 myUniLock(myMutex);
#include
#include
#include
#include
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!
unique_lock可以取代lock_guard,实现加锁、自动释放锁的作用。
unique_lock不同于lock_guard的独特性,在于第二个参数的设置
m_mutex.lock();
unique_lock<mutex> m_unique(m_mutex,adopt_lock);
、、、
//需要保护的数据
、、、
unique_lock<mutex> m_unique(m_mutex,try_to_lock);
if (m_unique.owns_lock()) {
printf("拿到锁,容器插入数据\n", );
} else {
printf("没有拿到锁,暂时不能插入数据\n" );
}
#include
#include
#include
#include
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;
}
这个例子需要更多的数据观察,不贴结果了。
unique_lock的成员函数
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
不用自己unlock();
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
//处理一些共享代码
myUniLock.unlock();
//处理一些非共享代码
myUniLock.lock();
//处理一些共享代码
因为一些非共享代码要处理,可以暂时先unlock(),用其他线程把它们处理了,处理完后再lock()。
如果拿不到锁,返回false,否则返回true。
lock的代码段越少,执行越快,整个程序的运行效率越高。
unique_lock myUniLock(myMutex)
;把myMutex和myUniLock绑定在了一起,也就是myUniLock拥有myMutex的所有权unique_lock<mutex> aFunction() {
unique_lock<mutex> myUniLock(myMutex);
//移动构造函数那里讲从函数返回一个局部的unique_lock对象是可以的
//返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
return myUniLock;
}
// 然后就可以在外层调用,在sbguard具有对myMutex的所有权
std::unique_lock<std::mutex> sbguard = aFunction();
略
这里讲得比较空洞,可以参考这篇博客:深入探索单例设计模式:以百度 Apollo 为例,3.2 多线程安全的懒汉式单例:单检锁实现
小节介绍了多线程加锁。
#include
#include
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() {
// 加锁,为什么不用 uni
// 每次调用 GetInstance 方法尝试获取实例时都会执行加锁操作,并在自析构
// std::lock_guard 对象时执行解锁操作,这必然会降低实例访问效率
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_) {
// 双检锁模式(Double-Checked Locking Pattern,DCLP)优化方案,即在
// GetInstance
// 中执行锁操作前,在最外层额外地进行一次实例指针的检查操作(“双检”的体现)
// 双检锁方法初衷虽好,但却破坏了多线程场景下的安全性,这是由动态内存分配时
// new 底层操作的非原子性导致的
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
#include
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_) {
// 双检锁模式(Double-Checked Locking Pattern,DCLP)优化方案,即在
// GetInstance
// 中执行锁操作前,在最外层额外地进行一次实例指针的检查操作(“双检”的体现)
// 双检锁方法初衷虽好,但却破坏了多线程场景下的安全性,这是由动态内存分配时
// new 底层操作的非原子性导致的
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;
}
使用原子变量确保多线程安全性
,后续再看。std::call_once()
函数模板,该函数的第一个参数为标记,第二个参数是一个函数名(如a())。
功能:能够保证函数a()只被调用一次。具备互斥锁的能力,而且比互斥锁消耗的资源更少,更高效。
call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。
多个线程同时执行时,一个线程会等待另一个线程先执行。
#include
#include
using namespace std;
std::once_flag g_flag;
class Singleton {
public:
// call_once保证其只被调用一次
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
#include
#include
#include
#include
#include
#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;
}
// There must be many copy-paste versions of these macros which are same
// things, undefine them to avoid conflict.
#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) { \
/*实例的唯一性通过局部静态(local static)的实例指针实现:*/ \
static classname *instance = nullptr; \
if (!instance && create_if_needed) { \
/*多线程安全性由 std::once_flag 和 std::call_once 保证:*/ \
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)
// -----------
/**
* @brief Singleton class without `Shutdown` method.
*/
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; }
// -----------
/**
* @brief Singleton class with `Shutdown` method.
*/
class SingletonB {
private:
~SingletonB() = default;
private:
static int num;
public:
// `Shutdown` method should be declared as `public` for type traits
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
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()将解锁互斥锁,并阻塞到本行
如果没有第二个参数,那么效果跟第二个参数lambda表达式返回false效果一样
当其他线程用notify_one() 将本wait(原来是睡着/阻塞)的状态唤醒后,wait就开始恢复干活了,恢复后wait干什么活?
#include
#include
#include
#include
#include
#include
#include
#include
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);
//尝试吧wait()唤醒,执行完这行,outMsgRecvQueue中的wait被唤醒
cond_var.notify_one();
//假如outMsgRecvQueue()正在处理一个事务,需要一段时间,
//而不是正卡在wait()那里等待你唤醒,那么此时这个notify_one()这个调用也许就没效果.
}
}
//把消息从消息队列中取出的线程
void outMsgRecvQueue() {
int command{};
while (true) {
std::unique_lock<mutex> outMutex(my_mutex);
// wait用来等一个东西
cond_var.wait(outMutex, [this]() {
if (!msgRecvQueue.empty()) return true;
return false;
});
// 流程只要能走到这里来,这个互斥锁一定是锁着的。同时msgRecvQueue至少是有一条数据的。
// 返回第一个元素,但不检查元素是否存在
command = msgRecvQueue.front();
//移除第一个元素,但不返回
msgRecvQueue.pop_front();
//因为unique_lock的灵活性,所以我们可以随时的unlock解锁,以免锁住太长时间
outMutex.unlock();
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
} // end while
} // end outMsgRecvQueue()
private:
std::list<int> msgRecvQueue; //容器(消息队列),专门代表玩家给我们发来的命令
std::mutex my_mutex;
std::condition_variable cond_var; //生成一个条件变量对象
}; // end A
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
主线程结束
1、如果wait有第二个参数就判断这个lambda表达式。
2、如果wait没有第二个参数,则wait返回,流程走下去。
流程只要走到了wait()下面则互斥锁一定被锁住了。
上面的代码可能导致出现一种情况:
因为outMsgRecvQueue()与inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在msgRecvQueue 中已经有了很多消息,但是,outMsgRecvQueue还是被唤醒一次只处理一条数据。这时可以考虑把outMsgRecvQueue多执行几次,或者对inMsgRecvQueue进行限流。
notify_one():通知一个线程的wait()
notify_all():通知所有线程的wait()
本节内容需要包含头文件#include
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
#include
// 耗时的工作
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;
}
// sleep 函数
void sleep(int milisec) {
std::this_thread::sleep_for(std::chrono::milliseconds(milisec));
}
int main() {
// 并行执行is_prime(334214467)
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
#include
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; //卡在这里等待mythread()执行完毕,拿到结果
//类成员函数
//第二个参数是对象引用才能保证线程里执行的是同一个对象
std::future<int> result2 = std::async(&A::mythread, &a, tmp);
cout << result2.get() << endl;
//或者result2.wait();
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类型(枚举类型),来达到一些特殊的目的:
#include
#include
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; //卡在这里等待mythread()执行完毕,拿到结果
cout << "good luck" << endl;
return 0;
}
运行结果:
mainthreadid = 140706613393216
continue........
mythread() startthreadid = 140706572023552
mythread() endthreadid = 140706572023552
5
good luck
#include
#include
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; //卡在这里等待mythread()执行完毕,拿到结果
cout << "good luck" << endl;
return 0;
}
运行结果:
mainthreadid = 140539030345536
continue........
mythread() startthreadid = 140539030345536
mythread() endthreadid = 140539030345536
5
good luck
永远都会先打印出continue…,然后才会打印出mythread() start和mythread() end等信息。
类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
#include
#include
#include
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;
//我们把函数mythread通过packaged_task包装起来
//参数是一个int,返回值类型是int
std::packaged_task<int(int)> mypt(mythread);
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
// std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
cout << result.get() << endl;
return 0;
}
运行结果:
mainthreadid = 139888845096768
1
mythread() startthreadid = 139888803727104
mythread() endthreadid = 139888803727104
5
#include
#include
#include
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();
// std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
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
#include
#include
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
#include
#include
#include
#include
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); //结果保存到了tmp这个对象中
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(); // promise和future绑定,用于获取线程返回值
auto result = fu1.get();
cout << "result = " << result << endl;
}
运行结果:
mythread() startthreadid = 140174314473216
mythread() endthreadid = 140174314473216
result = 180
总结:通过promise保存一个值,在将来某个时刻我们通过把一个future绑定到这个promise上,来得到绑定的值
注意:使用thread时,必须 join() 或者 detach() 否则程序会报异常
小结:
future成员函数:
获取结果
状态
wait_for使用例子
#include
#include
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;
// cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果
//等待1秒
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
#include
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 result = std::async(std::launch::deferred, mythread);
std::future<int> result = std::async(mythread);
cout << "continue........" << endl;
// cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果
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) {
//如果设置 std::future result = std::async(std::launch::deferred,
// mythread);,则本条件成立
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就报告了异常。
#include
#include
#include
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(); //判断future中的值是不是一个有效值
std::shared_future<int> result_s(
result.share()); //执行完毕后result_s里有值,而result里空了
// std::shared_future result_s(std::move(result));
//通过get_future返回值直接构造一个shared_future对象
// std::shared_future result_s(mypt.get_future());
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
互斥锁:多线程编程中 用于保护共享数据:先锁住, 操作共享数据, 解锁。
有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。
即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(一条C语句会被拆成3、4条汇编语句来执行,所以仍然有可能混乱)
#include
#include
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
#include
#include
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;
}
大家可以把原子操作理解成一种:不需要用到互斥锁加锁(无锁)技术的多线程并发编程方式。
原子操作:在多线程中不会被打断的程序执行片段。
从效率上来说,原子操作要比互斥锁的方式效率要高。
互斥锁的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。
原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态。
std::atomic来代表原子操作,是个类模板。其实std::atomic是用来封装某个类型的值的
需要添加#include
头文件
范例:
#include
#include
#include
using namespace std;
std::atomic<int> g_count(0); //封装了一个类型为int的 对象(值)
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
#include
#include
using namespace std;
std::atomic<bool> g_ifEnd(false); //封装了一个类型为bool的 对象(值)
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运行结束
总结:
1、原子操作一般用于计数或者统计(如累计发送多少个数据包,累计接收到了多少个数据包),多个线程一起统计,这种情况如果不使用原子操作会导致统计发生混乱。
2、写商业代码时,如果不确定结果的影响,最好自己先写一小段代码调试。或者不要使用。
#include
#include
#include
using namespace std;
std::atomic<int> g_count(0); //封装了一个类型为int的 对象(值)
void mythread1() {
for (int i = 0; i < 1000000; i++) {
//虽然g_count使用了原子操作模板,但是这种写法既读又写,
//会导致计数错误
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原子操作,针对++,–,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。
延迟调用参数 std::launch::deferred【延迟调用】,std::launch::async【强制创建一个线程】
std::async()我们一般不叫创建线程(他能够创建线程),我们一般叫它创建一个异步任务。
std::async和std::thread最明显的不同,就是 async 有时候并不创建新线程。
①如果用std::launch::deferred 来调用async?
②如果用std::launch::async来调用async?
③如果同时用 std::launch::async | std::launch::deferred
④不带额外参数 std::async(mythread),只给async 一个入口函数名,此时的系统给的默认值是 std::launch::async | std::launch::deferred 和 ③ 一样,有系统自行决定异步还是同步运行。
由于系统资源限制:
①如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。
②如果用std::async,一般就不会报异常,因为如果系统资源紧张,无法创建新线程的时候,async不加额外参数的调用方式就不会创建新线程。而是在后续调用get()请求结果时执行在这个调用get()的线程上。
③根据经验,一个程序中线程数量 不宜超过100~200 。
不加额外参数的async调用时让系统自行决定,是否创建新线程。
std::future
问题焦点在于这个写法,任务到底有没有被推迟执行。
通过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临界区,同一个线程是可以重复进入的,但是进入的次数与离开的次数必须相等。
C++互斥锁则不允许同一个线程重复加锁。
windows临界区是在windows编程中的内容,了解一下即可,效果几乎可以等同于c++11的mutex
包含#include
windows中的临界区同mutex一样,可以保护一个代码段。但windows的临界区可以进入多次,离开多次,但是进入的次数与离开的次数必须相等,不会引起程序报异常出错。
略。
std::mutex 独占式互斥锁
std::recursive_mutex:允许在同一个线程中同一个互斥锁多次被 lock() ,(但是递归加锁的次数是有限制的,太多可能会报异常),效率要比mutex低。
如果你真的用了 recursive_mutex 要考虑代码是否有优化空间,如果能调用一次 lock()就不要调用多次。
try_lock_for():
等待一段时间,如果拿到了锁,或者超时了未拿到锁,就继续执行(有选择执行)如下:
std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_for(timeout)) {
//......拿到锁返回ture
} 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)) {
//......拿到锁返回ture
} else {
std::chrono::milliseconds sleeptime(100);
std::this_thread::sleep_for(sleeptime);
}
两者的区别就是一个参数是时间段,一个参数是时间点