前言

多线程的学习,说实话我也不知道有什么用,其实我能用到的无非就是一个概念thread函数,然后再结合算法进行应用。而这种简单的应用成本几乎只需要一个小时了解一下概念就可以,根本不需要大费周章去完完整整的看视频进行学习。或许后续的编程学习会让我更加的深入?总之还是待观望。

参考博客

基本概念

并发

  • 定义:并发是指两个或更多的任务(独立的活动)在同一时间段内交替进行。具体来说,就是一个程序可以同时执行多个独立的任务。

  • 单核 CPU 并发:在过去,计算机使用单核 CPU(中央处理器),在某一时刻只能执行一个任务。操作系统通过调度程序实现任务切换(上下文切换),使得看起来像是多个任务同时进行,但实际上每个任务在短时间内交替执行。任务切换时需要保存变量状态和执行进度,这会产生时间开销。

  • 多核 CPU 并发:随着硬件的发展,出现了多处理器计算机,特别是在服务器和高性能计算领域。台式机中的多核 CPU(一个 CPU 内有多个运算核心)可以实现真正的并行处理,对操作系统来说,每个核心被视为独立的 CPU。这样,多个任务可以在不同的核心上同时执行,实现真正的并发。

  • 并发的目的:使用并发的主要目的是同时处理多个任务,以提高系统性能和效率。

可执行的程序

定义:可执行程序是一个可以被计算机执行的文件。

  • Windows:可执行程序的扩展名为 .exe

  • Linux:可以通过 ls -la 命令查看文件权限,具有 rwx(可读、可写、可执行)权限的文件是可执行文件。

进程

  • 定义:进程是一个运行中的可执行程序。在 Windows 上,可以通过双击可执行文件启动进程;在 Linux 上,可以使用 ./文件名 启动进程。

  • 当一个可执行程序运行起来后,就创建了一个进程。因此,进程就是运行中的可执行程序

线程

  • 主线程:每个进程(即运行中的可执行程序)都有一个唯一的主线程。当执行可执行程序时,产生一个进程,同时主线程随之启动。主线程负责执行 main 函数中的代码。

  • 线程的作用:线程是用来执行代码的。可以把线程理解为一条代码执行的通路。

  • 创建新线程:除了主线程外,还可以通过编程创建其他线程。每个新线程都可以执行不同的代码路径,这意味着在同一时刻可以处理多个不同的任务。

  • 线程数量的限制:线程数量并不是越多越好。每个线程需要独立的堆栈空间(大约 1MB),线程之间的切换需要保存很多中间状态,这些切换操作会耗费系统资源。因此,创建过多的线程会影响程序的性能

创建多线程

头文件:#include <thread>

使用函数对象创建线程

thread创建了线程mytobj,线程执行起点(入口)是myPrint

join意为汇合,子线程和主线程汇合join()一般用于阻塞主线程,使得主线程需要等待子线程执行完毕才结束;

detach()函数会将与主线程关联的thread对象失去与主线程的关联,该子线程会留驻在后台运行。脱缰野马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>

using namespace std;

void myprint()
{
cout << "This is my thread." << endl;

for (int i = 0; i < 5; i++)
{
cout << "线程中输出 i^2 " << i * i << endl;
}

cout << "My thread is finished" << endl;
}

int main()
{
thread mytobj(myprint);

mytobj.join();
// mytobj.detach();

cout << "BeiJing welcomes me?" << endl;
for (int i = 0; i < 5; i++)
{
cout << "主线程中输出 -i^2 " << -i * i << endl;
}

return 0;
}

mytobj.join();结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
This is my thread.
线程中输出 i^2 0
线程中输出 i^2 1
线程中输出 i^2 4
线程中输出 i^2 9
线程中输出 i^2 16
My thread is finished
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 -16

mytobj.detach();结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 This is my thread.
线程中输出 i^2 0
线程中输出 i^2 1
线程中输出 i^2 4
线程中输出 i^2 9
线程中输出 i^2 16
My thread is finished
-16

注:运行多次结果可能都不相同

注:调用detach()后不能再用join(),否则系统报错。

joinable()判断是否可以成功使用join()或者detach()

如果返回true,证明可以调用join()或者detach()

如果返回false,证明调用过join()或者detach()join()detach()都不能再调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <thread>

using namespace std;

void myprint()
{
cout << "This is my thread." << endl;

cout << "My thread is finished" << endl;
}

int main()
{
thread mytobj(myprint);

if (mytobj.joinable())
{
cout << "可以调用可以调用join()或者detach()" << endl;
}
else
{
cout << "不能调用可以调用join()或者detach()" << endl;
}

mytobj.detach();

if (mytobj.joinable())
{
cout << "可以调用可以调用join()或者detach()" << endl;
}
else
{
cout << "不能调用可以调用join()或者detach()" << endl;
}

cout << "BeiJing welcomes me?" << endl;

return 0;
}

结果:

1
2
3
4
5
可以调用可以调用join()或者detach()
This is my thread.
不能调用可以调用join()或者detach()
BeiJing welcomes me?
My thread is finished

使用类对象创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <thread>

using namespace std;

class Ta
{
public:
void operator()() //不能带参数
{
cout << "This is my thread." << endl;

for (int i = 0; i < 5; i++)
{
cout << "线程中输出 i^2 " << i * i << endl;
}

cout << "My thread is finished" << endl;
}
};

int main()
{
Ta ta;
thread myThread(ta);

myThread.join();
// myThread.detach();

cout << "BeiJing welcomes me?" << endl;
for (int i = 0; i < 5; i++)
{
cout << "主线程中输出 -i^2 " << -i * i << endl;
}

return 0;
}

mytobj.join();结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
This is my thread.
线程中输出 i^2 0
线程中输出 i^2 1
线程中输出 i^2 4
线程中输出 i^2 9
线程中输出 i^2 16
My thread is finished
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 -16

mytobj.detach();结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
BeiJing welcomes me?This is my thread.
线程中输出 i*mi 0
线程中输出 i*mi 6
线程中输出 i*mi 12
线程中输出 i*mi 18
线程中输出 i*mi 24
My thread is finished

主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 -16

注:主线程运行结束之后,main()函数中的变量会被销毁,但对象会被复制到线程中去,复制的对象依旧存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <thread>

using namespace std;

class Ta{
public:
int& mi;
Ta(int &i):mi(i)
{
cout << "Ta()构造函数被执行" << endl;
}
Ta(const Ta& ta) :mi(ta.mi)
{
cout << "Ta()拷贝构造函数被执行" << endl;
}
~Ta()
{
cout << "~Ta()析构函数被执行" << endl;
}

void operator()() //不能带参数
{
cout << "This is my thread." << endl;

for (int i = 0; i < 5; i++)
{
cout << "线程中输出 i*mi " << i * mi << endl;
}

cout << "My thread is finished" << endl;
}
};

int main()
{
int i = 6;
Ta ta(i); // 构造函数被调用
thread myThread(ta); // 调用拷贝构造函数
// myThread 结束后析构,内部拷贝的 Ta 对象析构

myThread.join(); // 线程对象 `myThread` 在 `join` 完成后析构,析构时内部拷贝的 `Ta` 对象析构
// myThread.detach();

cout << "BeiJing welcomes me?" << endl;
for (int i = 0; i < 5; i++)
{
cout << "主线程中输出 -i^2 " << -i * i << endl;
}

return 0; // 主线程结束,局部变量 `ta` 的析构
}

myThread.join()结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Ta()构造函数被执行
Ta()拷贝构造函数被执行
Ta()拷贝构造函数被执行
~Ta()析构函数被执行
This is my thread.
线程中输出 i*mi 0
线程中输出 i*mi 6
线程中输出 i*mi 12
线程中输出 i*mi 18
线程中输出 i*mi 24
My thread is finished
~Ta()析构函数被执行
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 -16
~Ta()析构函数被执行

myThread.detach()结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Ta()构造函数被执行
Ta()拷贝构造函数被执行
Ta()拷贝构造函数被执行
~Ta()析构函数被执行
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
This is my thread.主线程中输出 -i^2 -16

线程中输出 i*mi 0
线程中输出 i*mi 6
线程中输出 i*mi 12
线程中输出 i*mi 18
线程中输出 i*mi 24
My thread is finished
~Ta()析构函数被执行
~Ta()析构函数被执行

lamda表达式创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <thread>

using namespace std;

int main()
{
auto lambdaThread = [] {
cout << "This is my thread." << endl;

for (int i = 0; i < 5; i++)
{
cout << "线程中输出 i*i " << i * i << endl;
}

cout << "My thread is finished" << endl;
};

thread myThread(lambdaThread);
myThread.join();

cout << "BeiJing welcomes me?" << endl;
for (int i = 0; i < 5; i++)
{
cout << "主线程中输出 -i^2 " << -i * i << endl;
}

return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
This is my thread.
线程中输出 i*i 0
线程中输出 i*i 1
线程中输出 i*i 4
线程中输出 i*i 9
线程中输出 i*i 16
My thread is finished
BeiJing welcomes me?
主线程中输出 -i^2 0
主线程中输出 -i^2 -1
主线程中输出 -i^2 -4
主线程中输出 -i^2 -9
主线程中输出 -i^2 -16

传递临时对象

值传递与指针传递

当使用 detach() 分离线程时,传递参数应注意以下问题:

  • 传递 int 等简单类型参数时,建议使用值传递,而不使用引用传递,避免参数回收的问题(主线程首先结束,引用传递的参数被回收了,导致子线程的参数无效);

  • 当传递复杂类型参数时(如类等),应避免发生隐式类型转换(发生隐式类型转换时,可能会发生主线程结束了,要传递的参数还没进行隐式类型转换,而此时参数已经被回收了),可通过生成临时对象的方式(例如下面代码中的 std::string(s) 和 class(value)都在主线程中首先生成临时对象,再进行参数传递)来进行参数传递,同时在函数参数内用引用(例如 myprint 函数中 const myclass &cls)进行接收;

非必要情况,应尽量避免使用 detach() 来分离线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <thread>
using namespace std;

void myPrint(const int& i, char* pmybuf)
{
// 如果线程从主线程detach了
// i不是mvar真正的引用,实际上值传递,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用
cout << "i的地址为:" << &i << endl;

cout << "pmybuf的地址为:" << &pmybuf << endl; // pmybuf还是指向原来的字符串?
}

int main()
{
int mvar = 1;
int& mvary = mvar; // 引用
char mybuf[] = "this is a test";
cout << "mvar的地址为:" << &mvar << endl;
cout << "mvary的地址为:" << &mvary << endl;
cout << "mybuf的地址为:" << &mybuf << endl;

thread myThread(myPrint, mvar, mybuf); // 第一个参数是函数名,后两个参数是函数的参数
myThread.join();
// myThread.detach();

cout << "BeiJing welcomes me?" << endl;
}

结果:

1
2
3
4
5
6
mvar的地址为:0x16b4b6914
mvary的地址为:0x16b4b6914
pmybuf的地址为:0x16b4b6918
i的地址为:0x12df040d0
mybuf的地址为:0x16b53ef10
BeiJing welcomes me?

注:i并不是mvar的引用,实际上是值传递

但是指针传递时,在detach()的子线程中,会有main()函数运行完时被销毁的可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <thread>
#include <string>
using namespace std;

void myPrint(const int i, const string& pmybuf)
{
cout << i << endl;
cout << "i的地址为:" << &i << endl;
cout << pmybuf << endl;
cout << "pmybuf的地址为:" << &pmybuf << endl;
}

int main()
{
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test";
cout << "mvar的地址为:" << &mvar << endl;
cout << "mvary的地址为:" << &mvary << endl;
cout << "mybuf的地址为:" << &mybuf << endl;
// 如果detach了,这样仍然是不安全的
// 因为存在主线程运行完了,mybuf被回收了,系统采用mybuf隐式类型转换成string
// 推荐先创建一个临时对象thread myThread(myPrint, mvar, string(mybuf));
thread myThread(myPrint, mvar, mybuf);
myThread.join();
//myThread.detach();

cout << "BeiJing welcomes me?" << endl;
}

1
2
3
4
5
6
7
8
mvar的地址为:0x16f3f2914
mvary的地址为:0x16f3f2914
mybuf的地址为:0x16f3f2918
1
i的地址为:0x16f47aedc
this is a test
pmybuf的地址为:0x16f47af20
BeiJing welcomes me?

传递临时类对象

创建线程同时构造临时对象传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 创建自定义类
// 类型转换构造函数,把int整型转换为myclass类
class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << endl ;};
};


void myPrint(const int i, const myclass& pmybuf)
{
cout << i << endl;
cout << "i的地址为:" << &i << endl;
cout << "pmybuf的地址为:" << &pmybuf << endl;
}

int main()
{
int mvar = 1;
int myvar2 = 66;
thread myThread(myPrint, mvar, myclass(myvar2));
// myThread.join();
myThread.detach();

cout << "BeiJing welcomes me?" << endl;
}

1
2
3
4
5
6
7
8
构造函数myclass::myclass(int a)执行0x16d3ba91c
拷贝构造函数myclass::myclass(const int a)执行0x158605f14
析构函数myclass::~myclass()执行0x16d3ba91c
BeiJing welcomes me?
1
i的地址为:0x16d442f1c
pmybuf的地址为:0x158605f14
析构函数myclass::~myclass()执行0x158605f14

不使用临时对象传递

线程ID std::this_thread::get_id()

在子线程中构造对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 创建自定义类
// 类型转换构造函数,把int整型转换为myclass类
class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};
};


void myPrint(const myclass& pmybuf)
{
cout << "pmybuf的地址为:" << &pmybuf << " 子线程id为 " << std::this_thread::get_id() << endl;
}

int main()
{
cout<< "主线程id为 " << std::this_thread::get_id() << endl;
int myvar = 66;
thread myThread(myPrint, myvar); // 在子线程中构造对象
myThread.join();
// myThread.detach();

cout << "BeiJing welcomes me?" << endl;
}

结果:

1
2
3
4
5
主线程id为 0x1e8b9fac0
构造函数myclass::myclass(int a)执行0x16d196f3c threadid = 0x16d197000
pmybuf的地址为:0x16d196f3c 子线程id为 0x16d197000
析构函数myclass::~myclass()执行0x16d196f3c threadid = 0x16d197000
BeiJing welcomes me?

子线程需要使用主线程的变量 value 来进行构造和析构操作(具体可见构造和析构发生的线程 id 与主线程 id 不同)

当使用 detach 分离两个线程时,可能会出现主线程执行完毕了,主线程的变量 value 已被回收,而子线程来不及拷贝 value 变量来进行类的构造和析构操作;

使用临时对象传递

在主线程中构造对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 创建自定义类
// 类型转换构造函数,把int整型转换为myclass类
class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};
};


void myPrint(const myclass& pmybuf)
{
cout << "pmybuf的地址为:" << &pmybuf << " 子线程id为 " << std::this_thread::get_id() << endl;
}

int main()
{
cout<< "主线程id为 " << std::this_thread::get_id() << endl;
int myvar = 66;
thread myThread(myPrint, myclass(myvar)); // 所有的对象构建都在main函数中构建完毕
myThread.join();
// myThread.detach();

cout << "BeiJing welcomes me?" << endl;
}

结果:

1
2
3
4
5
6
7
主线程id为 0x1e8b9fac0
构造函数myclass::myclass(int a)执行0x16b872914 threadid = 0x1e8b9fac0
拷贝构造函数myclass::myclass(const int a)执行0x127e05f10 threadid = 0x1e8b9fac0
析构函数myclass::~myclass()执行0x16b872914 threadid = 0x1e8b9fac0
pmybuf的地址为:0x127e05f10 子线程id为 0x16b8fb000
析构函数myclass::~myclass()执行0x127e05f10 threadid = 0x16b8fb000
BeiJing welcomes me?

临时对象的构造和析构发生在主线程中,即使说主线程的变量value被回收,子线程已经拷贝了value的变量,不会因为主线程的结束而出问题

修改临时对象内参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 创建自定义类
// 类型转换构造函数,把int整型转换为myclass类
class myclass
{
public:
mutable int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};
};


void myPrint(const myclass& pmybuf)
{
pmybuf.m_i = 2333; // 修改类中的值
cout << "pmybuf的地址为:" << &pmybuf << " pmybuf的值为:" << pmybuf.m_i << " 子线程id为 " << std::this_thread::get_id() << endl;
}

int main()
{
cout<< "主线程id为 " << std::this_thread::get_id() << endl;
myclass A(666);
cout<< "类A的地址" << &A << " 类A的值" << A.m_i << endl;
thread myThread(myPrint, A);
myThread.join();
// myThread.detach();

cout<< "类A的值" << A.m_i << endl;

cout << "BeiJing welcomes me?" << endl;
}

结果:

1
2
3
4
5
6
7
8
主线程id为 0x1e8b9fac0
构造函数myclass::myclass(int a)执行0x16cf16924 threadid = 0x1e8b9fac0
类A的地址0x16cf16924 类A的值666
拷贝构造函数myclass::myclass(const int a)执行0x144e05f10 threadid = 0x1e8b9fac0
pmybuf的地址为:0x144e05f10 pmybuf的值为:2333 子线程id为 0x16cf9f000
析构函数myclass::~myclass()执行0x144e05f10 threadid = 0x16cf9f000
类A的值666
BeiJing welcomes me?

并没有修改main函数中类的值,只修改了拷贝的类中的值

std::ref函数能将地址传输

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <string>
using namespace std;

// 创建自定义类
// 类型转换构造函数,把int整型转换为myclass类
class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};
};


void myPrint(myclass& pmybuf)
{
pmybuf.m_i = 2333; // 修改类中的值
cout << "pmybuf的地址为:" << &pmybuf << " pmybuf的值为:" << pmybuf.m_i << " 子线程id为 " << std::this_thread::get_id() << endl;
}

int main()
{
cout<< "主线程id为 " << std::this_thread::get_id() << endl;
myclass A(666);
cout<< "类A的地址" << &A << " 类A的值" << A.m_i << endl;
thread myThread(myPrint, std::ref(A));
myThread.join();
// myThread.detach();

cout<< "类A的值" << A.m_i << endl;

cout << "BeiJing welcomes me?" << endl;
}

结果:

1
2
3
4
5
6
7
主线程id为 0x1e8b9fac0
构造函数myclass::myclass(int a)执行0x16dbfa924 threadid = 0x1e8b9fac0
类A的地址0x16dbfa924 类A的值666
pmybuf的地址为:0x16dbfa924 pmybuf的值为:2333 子线程id为 0x16dc83000
类A的值2333
BeiJing welcomes me?
析构函数myclass::~myclass()执行0x16dbfa924 threadid = 0x1e8b9fac0

修改了类中的值

智能指针传递临时对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <thread>
#include <memory>
using namespace std;

void myPrint(unique_ptr<int> ptn)
{
cout<< "子线程中的指针地址为" << ptn << endl;
cout << "thread_id = " << std::this_thread::get_id() << endl;
}

int main()
{
unique_ptr<int> up(new int(10));
// 独占式指针只能通过std::move()才可以传递给另一个指针
// 传递后up就指向空,新的ptn指向原来的内存
cout<< "主线程中的指针地址为" << up << endl;
cout << "thread_id = " << std::this_thread::get_id() << endl;
thread myThread(myPrint, std::move(up));

// 所以这时就不能用detach了,因为如果主线程先执行完,ptn指向的对象就被释放了
myThread.join();
// myThread.detach();

return 0;
}

1
2
3
4
主线程中的指针地址为0x142e05fa0
thread_id = 0x1e8b9fac0
子线程中的指针地址为0x142e05fa0
thread_id = 0x16b70f000

成员函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <thread>
#include <iostream>

using namespace std;

class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};

//线程的执行入口
void thread_work(int num)
{
cout << "[子线程 thread_work 执行]" << this << " threadid = "<< std::this_thread::get_id() << endl ;
}
};


int main()
{
myclass myobj(10);
// 第一个为线程的入口函数,第二个为类对象,第三个为函数的参数
thread mytobj(&myclass::thread_work, myobj, 15);

// 如果传ref指针,则用的就是myobj本身,此时用 detach() 则错了
// std::thread mytobj(&A::thread_work, std::ref(myobj), 15);

mytobj.join();
return 0;
}

thread mytobj(&myclass::thread_work, myobj, 15);结果:

1
2
3
4
5
构造函数myclass::myclass(int a)执行0x16ba22928  threadid = 0x1e8b9fac0
拷贝构造函数myclass::myclass(const int a)执行0x15b8040d8 threadid = 0x1e8b9fac0
[子线程 thread_work 执行]0x15b8040d8 threadid = 0x16baab000
析构函数myclass::~myclass()执行0x15b8040d8 threadid = 0x16baab000
析构函数myclass::~myclass()执行0x16ba22928 threadid = 0x1e8b9fac0

std::thread mytobj(&A::thread_work, std::ref(myobj), 15);结果:

1
2
3
构造函数myclass::myclass(int a)执行0x16fc2a928  threadid = 0x1e8b9fac0
[子线程 thread_work 执行]0x16fc2a928 threadid = 0x16fcb3000
析构函数myclass::~myclass()执行0x16fc2a928 threadid = 0x1e8b9fac0

发现std::ref没有调用拷贝构造函数

调用运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <thread>
#include <iostream>

using namespace std;

class myclass
{
public:
int m_i;
myclass(int a): m_i(a) { cout << "构造函数myclass::myclass(int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
myclass(const myclass &a): m_i(a.m_i) { cout << "拷贝构造函数myclass::myclass(const int a)执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;} ;
~myclass() { cout << "析构函数myclass::~myclass()执行" << this << " threadid = "<< std::this_thread::get_id() << endl ;};

//线程的执行入口
void thread_work(int num)
{
cout << "[子线程 thread_work 执行]" << this << " threadid = "<< std::this_thread::get_id() << endl ;
}

void operator()(int num)
{
cout << "[子线程()执行]" << this << " threadid = "<< std::this_thread::get_id() << endl ;
}
};


int main()
{
myclass myobj(10);
thread mytobj(myobj, 15);

// thread mytobj(std::ref(myobj), 15);

mytobj.join();
return 0;
}


thread mytobj(myobj, 15);结果:

1
2
3
4
5
构造函数myclass::myclass(int a)执行0x16b03e928  threadid = 0x1e8b9fac0
拷贝构造函数myclass::myclass(const int a)执行0x14fe05e38 threadid = 0x1e8b9fac0
[子线程()执行]0x14fe05e38 threadid = 0x16b0c7000
析构函数myclass::~myclass()执行0x14fe05e38 threadid = 0x16b0c7000
析构函数myclass::~myclass()执行0x16b03e928 threadid = 0x1e8b9fac0

thread mytobj(std::ref(myobj), 15);结果:

1
2
3
构造函数myclass::myclass(int a)执行0x16d85e928  threadid = 0x1e8b9fac0
[子线程()执行]0x16d85e928 threadid = 0x16d8e7000
析构函数myclass::~myclass()执行0x16d85e928 threadid = 0x1e8b9fac0

创建多个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <thread>
#include <iostream>
#include <vector>

using namespace std;

//线程入口函数
void myprint(int inum)
{
cout << "myprint线程开始执行了,线程编号 = " << inum << endl;

cout << "thread_id = " << std::this_thread::get_id() << endl ;
//...

cout << "myprint线程执行结束了,线程编号 = " << inum << endl;
}

int main()
{
vector<thread> mythreads;
//创建5个线程,线程入口函数统一使用 myprint
for (int i = 0; i < 5; i++)
{
//创建5个线程,同时这5个线程已经开始执行
//多个线程执行顺序是乱的,和操作系统内部对线程的运行调度机制有关
//主线程等待所有子线程运行结束,最后主线程结束,推荐join的写法,更容易写出稳定的代码
mythreads.push_back(thread(myprint, i));
}
//把thread对象放入容器中进行管理,比较方便
//使用迭代器取出每一个线程
for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
{
//等待10个线程都返回
iter->join();
}
cout << "BeiJing welcomes me?" << endl;
return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
myprint线程开始执行了,线程编号 = myprint线程开始执行了,线程编号 = 02
thread_id = 0x16d193000
myprint线程执行结束了,线程编号 = 0

thread_id = 0x16d2ab000
myprint线程执行结束了,线程编号 = myprint线程开始执行了,线程编号 = 21
myprint线程开始执行了,线程编号 = 3
thread_id =
myprint线程开始执行了,线程编号 = 0x16d337000
4thread_id = myprint线程执行结束了,线程编号 = 30x16d21f000


myprint线程执行结束了,线程编号 = 1thread_id =
0x16d3c3000
myprint线程执行结束了,线程编号 = 4
BeiJing welcomes me?

数据共享问题

只读数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <thread>
#include <iostream>
#include <vector>

using namespace std;

vector<int> e_v = { 1,2,3 }; // 共享数据


//线程入口函数
void myprint(int inum)
{
cout << "id 为" << std::this_thread::get_id() << "的线程 打印 g_v 的值" << e_v[0] << e_v[1] << e_v[2] << endl;
}

int main()
{
vector<thread> mythreads;
//创建5个线程,线程入口函数统一使用 myprint
for (int i = 0; i < 5; i++)
{
mythreads.push_back(thread(myprint, i));
}

for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
{
iter->join();
}
cout << "BeiJing welcomes me?" << endl;
return 0;
}


1
2
3
4
5
6
id 为id 为0x16f50b000的线程 打印 g_v 的值id 为0x16f623000的线程 打印 g_v 的值112233
id 为0x16f73b000的线程 打印 g_v 的值123

id 为0x16f6af000的线程 打印 g_v 的值123
0x16f597000的线程 打印 g_v 的值123
BeiJing welcomes me?

独占互斥量

  • 互斥量是一个类对象,理解成一把锁,多个线程尝试用lock()成员函数来尝试加锁头,只有一个线程能够锁定成功,成功的标志是返回,如果没有所成功,那么这个线程的执行流程就会卡在lock()这里不断尝试去锁这把锁;

  • 互斥量使用需要小心:只保护需要保护的数据,也必须保护全(保护多了影响效率,保护少了达不到保护效果),操作完以后要把锁解开,其他线程才能使用lock继续执行;

实际例子:网络游戏服务器有两个自己创建的线程一个线程:收集玩家发来的命令(简化问题:用一个数字代表),将命令数据写入一个队列一个线程:从队列中取出玩家发送来的命令,解析,执行玩家要干的动作

lock(),unlock()

  • 步骤:先lock(),操作共享数据,再unlock()

  • lock()unlock()要成对使用,每调用一次lock(),必须调用一次unlock()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <thread>
#include <mutex>
#include <iostream>
#include <list>

using namespace std;

// 成员函数作为线程函数的方法来写线程
class A
{
public:
// 线程一:从玩家收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock(); // 上锁
msgRecvQueue.push_back(i);
my_mutex.unlock(); // 解锁
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock(); // 上锁
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex.unlock(); // 解锁
return true;
}
my_mutex.unlock(); // 解锁
return false;
}
// 线程二:把数据从消息队列取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,释放一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}

private:
list<int> msgRecvQueue; // 队列:专用于代表玩家发送过来的命令
std::mutex my_mutex; // 创建一个互斥量
};

int main()
{
A myobja;
// 第二个参数时引用,才能保证线程里,用的时同一个对象
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

// 操作的时候用代码把共享数据锁住,其他想操作共享数据的线程必须等待
// 锁住后操作数据、解锁
// 解锁后其他数据锁住,操作数据、解锁
myOutMsgobj.join();
myInMsgobj.join();
return 0;
}

std::lock_guard类模板

std::lock_guard类模板:直接取代lock()和unlock();也就是说,使用了lock_guard()后就不能再使用lock()和unlock()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
td::lock_guard<std::mutex> sbguard(my_mutex);
msgRecvQueue.push_back(i);
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}

死锁

c++中的死锁(至少有两把锁):比如有两把锁,锁一,锁二,两个线程,线程A,线程B;(业务:需要把两把锁都锁上)

  • 线程A执行的时候,这个线程先锁锁一;然后去锁锁二;

  • 出现了上下文切换,线程A被切换走了,线程B开始执行,这个线程先锁锁二成功;然后线程B去锁锁一;

此时此刻,死锁就发生了

  • 线程A锁不了锁二,流程走不下去,所以锁一解不开;

  • 线程B锁不了锁一,流程走不下去,所以锁二解不开;

错误示范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <thread>
#include <mutex>
#include <iostream>
#include <list>

using namespace std;

//成员函数作为线程函数的方法来写线程
class A
{
public:
//线程一:从玩家收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
//先锁一,后锁二
my_mutex1.lock();
//执行.......
my_mutex2.lock();
msgRecvQueue.push_back(i);
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();

}
}
bool outMsgLULProc(int& command)
{
//先锁二,后锁一
my_mutex2.lock();
//执行.......
my_mutex1.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//unlock的顺序无所谓
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
//unlock的顺序无所谓
my_mutex1.unlock();
my_mutex2.unlock();
return false;
}
//线程二:把数据从消息队列取出的线程
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,释放一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}

private:
list<int> msgRecvQueue;
std::mutex my_mutex1; //创建第一个互斥量
std::mutex my_mutex2; //创建第二个互斥量
};

int main()
{
A myobja;

thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);

myOutMsgobj.join();
myInMsgobj.join();
return 0;
}

互斥量的lock()顺序一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
//先锁一,后锁二
my_mutex1.lock();
//执行.......
my_mutex2.lock();
msgRecvQueue.push_back(i);
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();

}
}
bool outMsgLULProc(int& command)
{
//先锁一,后锁二
my_mutex1.lock();
//执行.......
my_mutex2.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();
return true;
}
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();
return false;
}

std::lock()函数模板

std::lock()用于处理多个互斥量;一次锁住两个或者两个以上的互斥量(至少两个);(同时锁住多个互斥量的情况比较少见);

  • 不存在在多线程中,因为锁的顺序导致死锁的风险问题;

  • std::lock():如果互斥量中有一个每锁住,他就会释放自己锁住的,然后就等在那里,等所有互斥量都锁住,才往下走;

  • 要么两个互斥量都锁住或者两个互斥量都释放;如果只锁一个,另外一个没成功,则立即释放锁住的;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex1, my_mutex2);
msgRecvQueue.push_back(i);
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();

}
}
bool outMsgLULProc(int& command)
{
std::lock(my_mutex1, my_mutex2);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();
return true;
}
//unlock的顺序无所谓
my_mutex2.unlock();
my_mutex1.unlock();
return false;
}

std::lock_guard的std::adopt_lock参数

std::adopt_lock是一个结构体对象,起一个标记作用,表示互斥量已经进行lock了,不需要在构造函数里面在对其进行lock;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);

}
}
bool outMsgLULProc(int& command)
{
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}

unique_lock()类模板

unique_lock比lock_guard灵活很多(多出来很多用法),效率差一点

取代lock_guard()

1
2
3
4
5
6
7
8
9
10
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
// std::lock_guard<std::mutex> sbguard(my_mutex);
std::unique_lock<std::mutex> myUniLock(my_mutex);
msgRecvQueue.push_back(i);
}
}

参数

  1. std::adopt_lock

  • 表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了。

  • 前提:必须提前lock

1
2
3
4
5
6
7
8
9
10
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
std::unique_lock<std::mutex> myUniLock(my_mutex, std::adopt_lock);
msgRecvQueue.push_back(i);
}
}
  1. std::try_to_lock

尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里;使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方前提:不能提前lock(); owns_lock()方法判断是否拿到锁,如拿到返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行" << endl;
std::unique_lock<std::mutex> myUniLock(my_mutex, std::try_to_lock);
if (sbguard.owns_lock() == true)
{
cout<<"拿到了锁,执行命令,插入一个元素" << i << endl;
msgRecvQueue.push_back(i);
}
else
{
cout<<"inMsgRecvQueue()执行但没有拿到锁,执行其他命令"<<endl;
}
}
  1. std::defer_lock:

  • 如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex

  • 不给它加锁的目的是以后可以调用unique_lock的一些方法

  • 前提:不能提前lock

成员函数

lock():加锁

1
2
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();

不用自己unlock();

unlock():解锁

1
2
3
4
5
6
7
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();
//处理一些共享代码
myUniLock.unlock();
//处理一些非共享代码
myUniLock.lock();
//处理一些共享代码

因为一些非共享代码要处理,可以暂时先unlock(),用其他线程把它们处理了,处理完后再lock()

try_lock()

std::try_to_lock用法类似

release():

unique_lock<mutex> myUniLock(my_mutex);相当于把myMutex和myUniLock绑定在了一起,release()就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权

所有权由ptx接管,如果原来mutex对象处理加锁状态,就需要ptx在以后进行解锁了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行" << endl;
unique_lock<mutex> myUniLock(my_mutex);
cout<<"my_mutex的地址为" << &my_mutex << endl;
mutex* ptx = myUniLock.release();
cout<<"ptx的地址为" << ptx << endl;

msgRecvQueue.push_back(i);

ptx->unlock();
}
}

lock的代码段越少,执行越快,整个程序的运行效率越高。

  • 锁住的代码少,叫做粒度细,执行效率高;

  • 锁住的代码多,叫做粒度粗,执行效率低;

所有权的传递

unique_lock<mutex> myUniLock(myMutex);把myMutex和myUniLock绑定在了一起,也就是myUniLock拥有myMutex的所有权

  1. 使用move转移

myUniLock1拥有myMutex的所有权,myUniLock1可以把自己对myMutex的所有权转移,但是不能复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行" << endl;
unique_lock<mutex> myUniLock1(my_mutex);
cout<<"myUniLock1的地址为" << &myUniLock1 << endl;
unique_lock<mutex> myUniLock2(std::move(myUniLock1));
cout<<"myUniLock2的地址为" << &myUniLock2 << endl;

msgRecvQueue.push_back(i);
}
}
  1. 构造一个返回值函数转移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unique_lock<mutex> return_unique_lock()
{
unique_lock<mutex> myUniLock(my_mutex);
cout<<"函数中myUniLock的地址为" << &myUniLock << endl;
//移动构造函数那里讲从函数返回一个局部的unique_lock对象是可以的
//返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
return myUniLock;
}

// 线程一:从玩家收到的消息入到一个队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100; ++i)
{
cout << "inMsgRecvQueue()执行" << endl;
unique_lock<mutex> myUniLock = return_unique_lock();
cout<<"myUniLock的地址为" << &myUniLock << endl;

msgRecvQueue.push_back(i);
}
}

条件变量condition_variable

wait(), notify_one()

  • wait()用于等待,当lambda表达式返回true时,wait()直接返回;当lambda表达式返回false时,wait()将解锁互斥量,并在本行堵塞,堵塞直到某个线程调用notify_one()成员函数为止

  • 如果wait()没有第二个参数,则与lambda表达式返回false的效果一样,即在本行堵塞,直到某个线程调用notify_one()成员函数

  • 当其他线程用notify_one()堵塞的wait()唤醒后,wait()线程将不断尝试获取锁(注:不一定能狗立刻获取到锁)

  • 假如wait()所在线程正在执行其余代码段,并不处于wait()的状态,此时notify_one()无效果(相当于宣告一直没锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <condition_variable>

using namespace std;

class A{
public:
// 模型发送消息
void inMsgRecvQueue()
{
for(int i = 0; i < 100; i++)
{
cout << "Running inMsgRecvQueue(), insert one elem" << i << endl;
unique_lock<mutex> guard1(mutex1);
msgRecvQueue.push_back(i);

cond.notify_one(); // 将wait()所在线程唤醒
// 假如wait()所在线程正在执行其余代码段,并不处于wait()的状态,此时notify_one()无效果
}
return;
}
// 模拟取消息
void outMsgRecvQueue()
{
int command = 0;
while(true)
{
std::unique_lock<std::mutex> guard1(mutex1);
// wait()用于等待,当lambda表达式返回true时,wait()直接返回
// 当lambda表达式返回false时,wait()将解锁互斥量,并在本行堵塞
// 堵塞直到某个线程调用notify_one()成员函数为止
// 如果wait()没有第二个参数,则与lambda表达式返回false的效果一样
// 即在本行堵塞,直到某个线程调用notify_one()成员函数
// 当其他线程用notify_one()堵塞的wait()唤醒后,wait()线程将不断尝试获取锁
// 当wait()成功获取锁后,将加锁并执行里面的内容(重新判断lambda表达式)
cond.wait(guard1, [this]{
if(!msgRecvQueue.empty()) return true;
else return false; // 第二参数,判断要处理的公共数据是否存在
});

// lambda表达式返回true,执行下面的内容
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
guard1.unlock(); // unique_lock可以随时解锁
cout << "Running outMsgRecvQueue(), get one elem: "<< command << endl;
if(command == 999) break;
}
}

private:
mutex mutex1;
list<int> msgRecvQueue;
condition_variable cond;
};

int main()
{
A a;
std::thread OutMsg(&A::outMsgRecvQueue, &a);
std::thread InMsg(&A::inMsgRecvQueue, &a);
OutMsg.join();
InMsg.join();
return 0;
}

notify_all()

notify_all()可以同时唤醒多个线程,,当多个线程都会等待被唤醒时,所有线程都会被唤醒;(被唤醒的多个线程可能会竞争同一个互斥锁(假设线程共用一个锁))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <condition_variable>

using namespace std;

class A{
public:
// 模型发送消息
void inMsgRecvQueue()
{
for(int i = 0; i < 100; i++)
{
cout << "Running inMsgRecvQueue(), insert one elem" << i << endl;
unique_lock<mutex> guard1(mutex1);
msgRecvQueue.push_back(i);

cond.notify_all(); // 将wait()所在线程唤醒
// 假如wait()所在线程正在执行其余代码段,并不处于wait()的状态,此时notify_one()无效果
}
return;
}
// 模拟取消息
void outMsgRecvQueue()
{
int command = 0;
while(true)
{
std::unique_lock<std::mutex> guard1(mutex1);
// wait()用于等待,当lambda表达式返回true时,wait()直接返回
// 当lambda表达式返回false时,wait()将解锁互斥量,并在本行堵塞
// 堵塞直到某个线程调用notify_one()成员函数为止
// 如果wait()没有第二个参数,则与lambda表达式返回false的效果一样
// 即在本行堵塞,直到某个线程调用notify_one()成员函数
// 当其他线程用notify_one()堵塞的wait()唤醒后,wait()线程将不断尝试获取锁
// 当wait()成功获取锁后,将加锁并执行里面的内容(重新判断lambda表达式)
cond.wait(guard1, [this]{
if(!msgRecvQueue.empty()) return true;
else return false;
});

// lambda表达式返回true,执行下面的内容
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
cout << "Running outMsgRecvQueue(), get one elem: "<< command << " 线程id" << this_thread::get_id() << endl;
guard1.unlock(); // unique_lock可以随时解锁
if(command == 999) break;
}
}

private:
mutex mutex1;
list<int> msgRecvQueue;
condition_variable cond;
};

int main()
{
A a;
std::thread OutMsg1(&A::outMsgRecvQueue, &a);
std::thread OutMsg2(&A::outMsgRecvQueue, &a);
std::thread InMsg(&A::inMsgRecvQueue, &a);
OutMsg1.join();
OutMsg2.join();
InMsg.join();
return 0;
}

超时锁

std::timed_mutex带有超时功能的独占互斥量

try_lock_for()

try_lock_for()等待一段时间来尝试获取锁,若无法获取锁,则线程继续执行(跳过临界区的内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void inMsgRecvQueue()
{
for(int i = 0; i < 100; i++)
{
std::chrono::seconds timeout(100); // 100ms
if(my_mutex.try_lock_for(timeout))
{
// 100ms 内拿到了锁
std::cout << "inMsgRecvQueue()执行,插入一个元素:" << i << std::endl;
msgRecvqueue.push_back(i); // 消息队列存储消息
my_mutex.unlock();
}
else
{
// 获取不成功
std::chrono::seconds sleeptime(100); // 线程休息100ms
std::this_thread::sleep_for(sleeptime);
std::cout << "can not get the lock" << std::endl;
}
}
}

try_lock_until()

try_lock_until()是直到某一时间内都在尝试获取锁,如果超过了规定的时间点仍无法获取锁,线程就会继续执行(跳过临界区的内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void inMsgRecvQueue()
{
for(int i = 0; i < 100; i++)
{
std::chrono::seconds timeout(100); // 100ms
if(my_mutex.try_lock_until(std::chrono::steady_clock::now() + timeout)
{
// 100ms 内拿到了锁
std::cout << "inMsgRecvQueue()执行,插入一个元素:" << i << std::endl;
msgRecvqueue.push_back(i); // 消息队列存储消息
my_mutex.unlock();
}
else
{
// 获取不成功
std::chrono::seconds sleeptime(100); // 线程休息100ms
std::this_thread::sleep_for(sleeptime);
std::cout << "can not get the lock" << std::endl;
}
}
}

递归独占互斥量

std::recursive_mutex创建递归的独占互斥量,允许一个线程对其多次加锁lock()和解锁unlock()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

std::recursive_mutex mtx;

void functionB() {
std::lock_guard<std::recursive_mutex> lock(mtx);
cout << "functionB() called by thread " << std::this_thread::get_id() << endl;
}

void functionA() {
std::lock_guard<std::recursive_mutex> lock(mtx);
cout << "functionA() called by thread " << std::this_thread::get_id() << endl;
functionB(); // 在持有锁的情况下调用另一个可能加锁的函数
}

void threadFunc() {
functionA();
}

int main() {
std::thread t1(threadFunc);
std::thread t2(threadFunc);

t1.join();
t2.join();

return 0;
}

单例设计模式共享数据

单例设计模型

单例设计模式要求某一个类最多创建一个对象,这个对象即单例对象(全局唯一实例对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>

using namespace std;

class MyCAS{
public:
static MyCAS *GetInstance(){
if(m_instance == NULL){
m_instance = new MyCAS();
static CGar c1;
}
return m_instance;
}

void func(){
std::cout << "test sample!" << std::endl;
}

class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance)
{
cout << "调用子类的析构函数,释放m_instance" << endl;
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};

private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量

};

// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;

int main()
{
MyCAS *sample1 = MyCAS::GetInstance(); // 创建对象,返回该类对象的指针
MyCAS *sample2 = MyCAS::GetInstance(); // 创建对象,返回该类对象的指针
cout << sample1 << " " << sample2 << endl; // 两个指针指向同一个对象

sample1->func(); // 调用成员函数测试
return 0;
}
1
2
3
0x15be05e70 0x15be05e70
test sample!
调用子类的析构函数,释放m_instance

数据竞争

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

class MyCAS{
public:
static MyCAS *GetInstance()
{
if(m_instance == NULL)
{
m_instance = new MyCAS();
static CGar c1;
}
return m_instance;
}

void func(){
std::cout << "test sample!" << std::endl;
}

class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance){
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};

private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量
};

// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;

// 线程入口函数
void mythread(){
cout << "start thread" << std::endl;
MyCAS *p_a = MyCAS::GetInstance();
p_a->func();
cout << "thread end" << std::endl;
return;
}

int main()
{
std::thread thread1(mythread);
std::thread thread2(mythread);
thread1.join();
thread2.join();
return 0;
}

问题:可能两个线程会同时进入if(m_instance == NULL)的判断语句,导致多个线程同时创建类对象的情况

解决方法:加入互斥量,保证只有一个if(m_instance == NULL)会执行

1
2
3
4
5
6
7
8
9
10
static MyCAS *GetInstance()
{
std::unique_lock<std::mutex> guard1(mutex1); // 自动加锁和解锁
if(m_instance == NULL)
{
m_instance = new MyCAS();
static CGar c1;
}
return m_instance;
}

问题:线程每一次创建时都需要调用std::unique_lock<std::mutex> guard1(mutex1);,效率低下

双重检验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static MyCAS *GetInstance()
{
// 双重检查锁
if(m_instance == NULL)
{
std::unique_lock<std::mutex> guard1(mutex1); // 自动加锁和解锁
if(m_instance == NULL)
{
m_instance = new MyCAS();
static CGar c1;
}
}
return m_instance;
}

call_once()

std::call_once()的功能是确保函数 func() 只会被调用一次;(即 std::call_once() 具有互斥量的能力,相对于互斥量其消耗的资源更少)

std::call_once() 需要和一个标记进行结合使用,这个标记决定对象的函数 func() 是否被调用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <thread>
#include <mutex>

std::once_flag g_flag; // 系统定义的标记

class MyCAS{
private:
static void CreateInstance(){
m_instance = new MyCAS();
static CGar c1;
}

public:
static MyCAS *GetInstance(){
// std::call_once 确保创建单例对象的函数只会被调用一次
std::call_once(g_flag, CreateInstance);
return m_instance;
}

void func(){
std::cout << "test sample!" << std::endl;
}

class CGar{ // 类中套类,用于释放对象
public:
~CGar(){
if(MyCAS::GetInstance){
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};

private:
MyCAS(){}; // 私有化成员变量,不能通过构造函数来创建对象
static MyCAS *m_instance; // 静态成员变量
};

// 静态数据成员类外初始化
MyCAS* MyCAS::m_instance = NULL;

// 线程入口函数
void mythread(){
std::cout << "start thread" << std::endl;
MyCAS *p_a = MyCAS::GetInstance();
p_a->func();
std::cout << "thread end" << std::endl;
return;
}

int main()
{
std::thread thread1(mythread);
std::thread thread2(mythread);
thread1.join();
thread2.join();
return 0;
}

原子操作

原子操作std::atomic是一种在多线程环境下不可分割的操作,确保操作不会被其他线程中断。这对于保证数据一致性和避免数据竞争非常重要。原子操作通常适用于对单个变量的操作,而不是一段代码或多个变量(使用互斥量)。

  1. 高效性:原子操作通常比使用互斥量(std::mutex)进行加锁和解锁更高效,因为原子操作在硬件层面提供了支持。

  2. 线程安全:原子操作保证了在多线程环境下操作的完整性,避免了数据竞争问题。

  3. 简洁性:使用原子操作可以简化代码,因为不需要显式地进行锁定和解锁操作。

原子操作的适用场景包括但不限于以下几种:

  1. 计数器:如全局计数器、访问计数器等。

  2. 标志变量:如状态标志、完成标志等。

  3. 指针操作:如原子指针的读取和写入。

  4. 简洁的条件检查和交换操作:如比较并交换(compare-and-swap)。

计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

std::atomic<int> counter(0);

void incrementCounter(int n)
{
for (int i = 0; i < n; ++i) {
counter++;
}
}

int main()
{
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i)
{
threads.push_back(std::thread(incrementCounter, 1000));
}

for (auto& t : threads)
{
t.join();
}

std::cout << "Final counter value: " << counter << std::endl;
return 0;
}

标志变量

原子操作可以用于设置和检查标志变量,例如在多线程环境下通知某些状态变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<bool> ready(false);

void waitForReady()
{
std::chrono::seconds dura(1);
while (!ready)
{
cout << "thread runs 1s" << endl;
std::this_thread::sleep_for(dura);
}
std::cout << "Thread " << std::this_thread::get_id() << " proceeds." << std::endl;
}

int main()
{
std::thread t1(waitForReady);
std::thread t2(waitForReady);

std::this_thread::sleep_for(std::chrono::seconds(5));
ready = true; // 设置标志变量

t1.join();
t2.join();

return 0;
}

指针操作

.store()以原子方式写入内容,.load()以原子方式读对象的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int*> atomic_ptr(nullptr);

void setPointer(int* ptr)
{
atomic_ptr.store(ptr);
}

void usePointer()
{
while (!atomic_ptr.load())
{
std::this_thread::yield();
}
cout << "Pointer value: " << *atomic_ptr.load() << endl;
}

int main()
{
int value = 42;
std::thread t1(usePointer);
std::thread t2(setPointer, &value);

t1.join();
t2.join();

return 0;
}

比较并交换

expected:这是一个左值引用,表示预期的旧值。如果原子变量的当前值与 expected 相等,则将其更新为 desired,否则将原子变量的当前值写回 expected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int> atomic_val(0);

void compareAndSwap(int oldVal, int newVal)
{
int expected = oldVal; // 初始化 expected 为 oldVal
if (atomic_val.compare_exchange_strong(expected, newVal))
{
// 如果 atomic_val 当前值等于 expected(即 oldVal),则将其更新为 newVal
cout << "Thread " << std::this_thread::get_id() << " updated the value to " << newVal << endl;
}
else
{
// 否则,将 atomic_val 当前值写回 expected
cout << "Thread " << std::this_thread::get_id() << " failed to update the value. Current value is " << atomic_val.load() << endl;
}
}

int main()
{
std::thread t1(compareAndSwap, 0, 10);
std::thread t2(compareAndSwap, 0, 20);

t1.join();
t2.join();

cout << "Final value: " << atomic_val.load() << endl;
return 0;
}

future成员函数

std::async

std::async 创建一个异步任务,其不一定会创建一个新线程去执行该任务;

传递普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <thread>
#include <mutex>
#include <future>

using namespace std;

int mythread()
{
cout << "thread start, id: " << std::this_thread::get_id() << endl;
int value = 5000; // 休息5s
std::chrono::microseconds dura(value); // rest
std::this_thread::sleep_for(dura);
cout << "thread end, id: " << this_thread::get_id() << endl;
return 5;
}

int main()
{
// std::async用于启动一个异步任务,并返回一个std::future对象
// std::future对象里含有异步任务线程入口函数的结果
cout << "main thread id: " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread); // 创建一个线程并开始执行

// result.get()等待thread()执行完毕获取结果后,主线程才继续往下执行
cout << "resule.get(): " << result.get() << endl;
// get()只能调用一次不能调用多次

// result.wait() // 等待线程返回,但不返回结果

cout << "main thread continue ..." << endl;
return 0;
}
1
2
3
4
5
main thread id: 0x1fc76fac0
resule.get(): thread start, id: 0x16b143000
thread end, id: 0x16b143000
5
main thread continue ...

传递类成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <thread>
#include <mutex>
#include <future>

using namespace std;

class A
{
public:
int mythread(int mypar)
{
cout << mypar << endl;
cout << "thread start, id: " << std::this_thread::get_id() << endl;
int value = 5000; // 休息5s
std::chrono::microseconds dura(value); // rest
std::this_thread::sleep_for(dura);
cout << "thread end, id: " << this_thread::get_id() << endl;
return 5;
}

};

int main()
{
A a;
int temp = 666;
// std::async用于启动一个异步任务,并返回一个std::future对象
// std::future对象里含有异步任务线程入口函数的结果
cout << "main thread id: " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(&A::mythread, &a, temp); // 第二个参数是对象引用

// result.get()等待thread()执行完毕获取结果后,主线程才继续往下执行
cout << "resule.get(): " << result.get() << endl;

// result.wait() // 等待线程返回,但不返回结果

cout << "main thread continue ..." << endl;
return 0;
}
1
2
3
4
5
6
main thread id: 0x1fc76fac0
resule.get(): 666
thread start, id: 0x16bc5f000
thread end, id: 0x16bc5f000
5
main thread continue ...

延迟调用

std::launch::deferred表示调用线程入口函数将会被延迟到std::futurewait()get()调用,当wait()或者get()没有被调用时,线程入口函数不会被调用(线程不会被创建)

1
std::future<int> result = std::async(std::launch::deferred, &A::mythread, &a, temp);
1
2
3
4
5
6
main thread id: 0x1fc76fac0
resule.get(): 666
thread start, id: 0x1fc76fac0
thread end, id: 0x1fc76fac0
5
main thread continue ...

并没有创建新线程,是在主线程中调用的线程入口函数

参数std::launch::async,操作系统会强制创建一个新线程来执行异步任务(同步运行)

默认参数是std::launch::async | std::launch::deferred,操作系统会同步运行或者延迟调用

std::future_status

std::future_status 包含以下三个值:

  1. std::future_status::ready:表示异步操作已经完成,结果已经准备好,可以调用 future.get() 来获取结果。

  2. std::future_status::timeout:表示在指定的等待时间内,异步操作还没有完成。

  3. std::future_status::deferred:表示异步操作被延迟执行,仅在调用 future.get()future.wait() 时才会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <thread>
#include <mutex>
#include <future>

using namespace std;

class Sample
{
public:
// 线程入口函数
int thread(int value)
{
cout << "thread id: " << std::this_thread::get_id() << endl;
std::chrono::seconds dura(value); // rest
std::this_thread::sleep_for(dura);
return 5;
}
};

int main()
{
Sample sample;
int value = 3;
cout << "main thread id: " << std::this_thread::get_id() << endl;

// 启动异步操作
std::future<int> result = std::async(&Sample::thread, &sample, value);
// std::launch::deferred 设置延迟执行状态
// std::future<int> result = std::async(std::launch::deferred, &Sample::thread, &sample, value);

// 等待 1 秒钟
std::future_status status = result.wait_for(std::chrono::seconds(1));

if (status == std::future_status::timeout)
{
// 超时,因为线程里面睡眠了 3 秒
cout << "thread timeout!" << endl;
}
else if(status == std::future_status::ready)
{
// 线程在等待期间成功返回
cout << "result.get(): " << result.get() << endl;
}
else if(status == std::future_status::deferred)
{
// 延迟执行状态
cout << "deferred, result.get(): " << result.get() << endl;
}

std::cout << "main thread continue ..." << std::endl;
return 0;
}

Thread与saync区别

如果使用std::thread创建的线程太多导致系统资源紧张,可能创建失败,系统报告异常

如果使用std::async创建的任务过多导致系统资源紧张,不会报告异常不会崩溃。如果系统因为资源紧张无法创建新线程的时候,std::async不会创建新线程,而是后续调用result.get()请求结果,异步任务就运行在result.get()语句所在的线程上。

判断异步任务的执行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <future>

using namespace std;

int threadFunc()
{
cout << "Thread function id: " << std::this_thread::get_id() << endl;
return -1;
}

int main()
{
cout << "Main thread id: " << std::this_thread::get_id() << endl;

// 启动异步任务
std::future<int> result = std::async(std::launch::deferred | std::launch::async, threadFunc);

// 等待0秒,立即检查异步任务的状态
std::future_status status = result.wait_for(std::chrono::seconds(0));

if (status == std::future_status::deferred)
{
// 异步任务会延迟执行
cout << "Task is deferred, running in the main thread." << endl;
cout << "Result: " << result.get() << endl;
}
else
{
// 创建新线程来执行异步任务
cout << "Task is running in a new thread." << endl;
cout << "Result: " << result.get() << endl; // 获取结果
}

return 0;
}

std::shared_future

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <thread>
#include <future>
#include <vector>
#include <chrono>

using namespace std;

// 线程入口函数,模拟一些计算任务
int compute(int value)
{
cout << "Computing in thread id: " << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::seconds(value)); // 模拟计算
return value * 2;
}

void printResult(shared_future<int> fut, int threadNum)
{
cout << "Thread " << threadNum << " id: " << std::this_thread::get_id() << " waiting for result..." << endl;
int result = fut.get(); // 获取共享结果
cout << "Thread " << threadNum << " received result: " << result << endl;
}

int main()
{
int value = 3;
cout << "Main thread id: " << std::this_thread::get_id() << endl;

// 使用 std::async 启动异步操作,并获取 future 对象
std::future<int> fut = std::async(compute, value);

// 将 future 转换为 shared_future
std::shared_future<int> sharedFut = fut.share();

// 创建多个线程,使用 shared_future 来获取结果
std::vector<std::thread> threads;
for (int i = 1; i <= 3; ++i) {
threads.emplace_back(printResult, sharedFut, i);
}

// 等待所有线程完成
for (auto& t : threads) {
t.join();
}

cout << "Main thread continue ..." << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
Main thread id: 0x1fc76fac0
Computing in thread id: 0x16dc07000
Thread 1 id: 0x16dc93000 waiting for result...
Thread 2 id: 0x16dd1f000 waiting for result...
Thread 3 id: 0x16ddab000 waiting for result...
Thread Thread 12 received result: received result: 66

Thread 3 received result: 6
Main thread continue ...

std::packaged_task

std::packaged_task用于打包任务,其包装各种可调用对象,方便后续作为线程入口函数

包装普通子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <thread>
#include <mutex>
#include <future>

using namespace std;

int mythread(int mypar)
{
cout << mypar << endl;
cout << "thread start, id: " << std::this_thread::get_id() << endl;
int value = 5000; // 休息5s
std::chrono::microseconds dura(value); // rest
std::this_thread::sleep_for(dura);
cout << "thread end, id: " << this_thread::get_id() << endl;
return 5;
}

int main()
{
cout << "main thread id: " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread); // 把函数mythread通过packaged_task包装起来

std::thread t1(std::ref(mypt), 666); // 线程开始执行,第二个参数作为线程入口函数的参数
t1.join();
std::future<int> result = mypt.get_future(); // std::future对象里包含有线程入口函数的返回结果
cout << "result.get(): " << result.get() << endl;

cout << "main thread continue ..." << endl;
return 0;
}

包装lamda表达式

1
2
3
4
5
6
7
8
9
std::packaged_task<int(int)> mypt([](int mypar){
cout << mypar << endl;
cout << "thread start, id: " << std::this_thread::get_id() << endl;
int value = 5000; // 休息5s
std::chrono::microseconds dura(value); // rest
std::this_thread::sleep_for(dura);
cout << "thread end, id: " << this_thread::get_id() << endl;
return 5;
});

直接调用包装函数

1
2
3
mypt(666);
std::future<int> result = mypt.get_future();
cout << "result.get(): " << result.get() << endl;
1
2
3
4
5
6
main thread id: 0x1fc76fac0
666
thread start, id: 0x1fc76fac0
thread end, id: 0x1fc76fac0
result.get(): 5
main thread continue ...

没有创建子线程,都在主线程中执行

std::promise()

std::promise用于在其他线程中使用某个线程中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <future>

using namespace std;

void thread1_func(promise<int>& prom)
{
cout << "thread1_func thread id: " << std::this_thread::get_id() << endl;
// 模拟一些计算
std::this_thread::sleep_for(std::chrono::milliseconds(100));
int result = 42;
prom.set_value(result);
cout << "Thread 1: value set to " << result << endl;
}

void thread2_func(future<int>& fut)
{
cout << "thread2_func thread id: " << std::this_thread::get_id() << endl;
int value = fut.get(); // 阻塞等待,直到获取值
cout << "Thread 2: value read as " << value << endl;
}

int main()
{
cout << "main thread id: " << std::this_thread::get_id() << endl;
promise<int> prom;
future<int> fut = prom.get_future();

thread t1(thread1_func, std::ref(prom));
thread t2(thread2_func, std::ref(fut));

t1.join();
t2.join();

return 0;
}
1
2
3
4
5
main thread id: 0x1fc76fac0
thread1_func thread id: 0x16f7ff000
thread2_func thread id: 0x16f88b000
Thread 1: value set to 42
Thread 2: value read as 42