「C++ 多线程」std::thread 线程创建方法

文章大图来源:pixiv_id=105375867

1 普通函数调用创建线程

1.1 基本方式

定义一个函数作为线程接口函数,例如 foo()。我们可以通过 std::thread t(foo) 来创建线程 t。线程 t 将执行 foo() 函数,与主线程并发执行。

线程构造函数 std::thread 需要传入的是 函数指针,其指向的是函数所在的地址。我们只需要传入定义的函数的名称 foo 即可。

如果函数带有参数,比如定义的函数为 void foo(int a, std::string s) 之类的,那么在传入的函数指针参数后面依次跟上对应的函数参数即可。假如我们有定义好的 int 类型变量 numstd::string 类型变量 str,可以使用如下方式来创建线程:

1
std::thread t(foo, num, str);

当然,也可以采用临时变量,比如下面这种方式:

1
std::thread t(foo, 1, "abc");

1.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>

void foo(int a, std::string s){
std::cout << a << " " << s << "\n";
}

int main(){
int num = 520;
std::string str = "Marisa";

// 传入函数指针 foo,以及函数参数
std::thread t(foo, num, str);
t.join();

// 也可以是临时变量
std::thread t1(foo, 521, "Alice");
t1.join();

return 0;
}

运行结果如下:

2 lambda 表达式创建线程

2.1 基本方式

lambda 表达式是 C++ 11 引入的一个强大的特性,也即匿名函数。lambda 表达式的基本形式为 [](int a, int b, ...) {... }[] 部分表示捕获列表(此处捕获列表为空,如果要直接使用作用域内变量可以使用按引用捕获加上 & 等),(int a, int b, ...) 部分表示参数列表。

假如定义 lambda 表达式如下:

1
2
3
[](int a, int b){
std::cout << a << ' ' << b << "\n";
}

使用如下方式可以使用 lambda 表达式创建线程:

1
2
3
std::thread t([](int a, int b){
std::cout << a << ' ' << b << "\n";
}, 1, 2); // 后面加上传递的参数

或者在别处定义一个 lambda 表达式 foo,创建线程再调用该 lambda 表达式:

1
2
3
4
5
auto foo = [](int a, int b){
std::cout << a << ' ' << b << "\n";
};

std::thread t(foo, 1, 2); // 后面加上传递的参数

2.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>

int main(){
// (1)
std::thread t([](int a, int b){
std::cout << "Marisa " << a << "\n";
std::cout << "Alice " << b << "\n";
}, 1314, 520);
t.join();

// (2)
auto foo = [](int a, int b){
std::cout << "Marisa " << a << "\n";
std::cout << "Alice " << b << "\n";
};

std::thread t2(foo, 520, 1314);
t2.join();

return 0;
}

运行结果如下:

3 仿函数(函数对象)创建线程

3.1 基本方式

定义一个类 MyClass(或者结构体),在类 MyClass 中重载 () 运算符,使得类的对象可以像函数一样被调用,即 仿函数

在主函数中,创建一个 MyClass 类的对象 obj,我们可以将类对象 obj 作为线程接口函数,以 std::thread t(obj) 的形式创建线程。如果重载运算符函数有参数,加上后面再加上传递的参数即可。

3.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>

class MyClass{
int val;
public:
MyClass(int n): val(n) {}
void operator() (int a) {
for(int i = 1; i <= val; i ++ ){
std::cout << "Alice Margatroid " << a << "\n";
}
}
};

int main(){
MyClass obj(5);
std::thread t(obj, 520);
t.join();

return 0;
}

4 类成员函数创建线程

4.1 基本方式

定义一个类 MyClass,包含一个成员函数 foo()。在创建线程时,需要传递类成员函数指针、类对象(一般会使用对象的引用)以及类成员函数所需参数作为参数。

假如创建了一个 MyClass 类对象 obj,类中有一个成员函数为 void foo(int a),那么用如下形式创建线程:

1
std::thread t(&MyClass::foo, &obj, 5);

一般情况下,我们采用加上引用操作符 & 传递对象的引用,这样可以避免对象的拷贝(调用拷贝构造函数),以提升效率。也可以采用加上 std::ref 达到几乎同样的效果

&obj 本质上就是获取 obj 的地址。C++ 语法规则规定可以在使用类成员函数创建线程时,直接 加上 & 来传递成员函数指针以及对象的引用。这和线程接口函数传递引用是不一样的(一般情况下,线程接口函数传递普通变量的引用必须用 std::ref)。

4.2 示例

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>

class MyClass{
int val;
public:
// 构造函数
MyClass(int n): val(n){
std::cout << "build function thread id: " << std::this_thread::get_id() << "\n";
}

// 拷贝构造函数
MyClass(const MyClass& other){
val = other.val;
std::cout << "copy build function thread id: " << std::this_thread::get_id() << "\n";
}

// 析构函数
~MyClass(){
std::cout << "~function thread id: " << std::this_thread::get_id() << "\n";
}

void foo(int a){
std::cout << "foo(): thread starts, thread id = " << std::this_thread::get_id() << "\n";
for(int i = 1; i <= val; i ++ ){
std::cout << "Marisa " << a << "\n";
}
}
};

int main(){
MyClass obj(5);
// 传递类成员函数和对象的引用
std::thread t(&MyClass::foo, &obj, 520);
t.join();

return 0;
}

运行结果如下:

参考

std::thread cppreference


「C++ 多线程」std::thread 线程创建方法
https://marisamagic.github.io/2024/12/20/20241220/
作者
MarisaMagic
发布于
2024年12月20日
许可协议