概览
从C++11开始提供了线程的支持,终于可以方便的编写跨平台的线程代码了。除了std::thread类,还提供了许多其它便利同步的机制,本篇总结是C++11学习笔记系列的首篇总结。
std::thread
std::thread定义在<thread>
中,提供了方便的创建线程的功能。
类定义
1 | class thread |
从定义中我们可以得知:
- std::thread不支持拷贝语义。
- std::thread支持移动语义。
各个成员函数的简单介绍
- join() 可以用来等待线程结束,只能调用一次。
- joinable()是否与某个有效的线程关联。
- detach() 与当前线程分离。
- swap() 与另外一个std::thread交换。
- get_id()获取id。
- native_handle() 返回平台相关的数据,windows下是HANDLE。
- hardware_concurrency() 返回可并行运行的线程数量,只能作为一个参考。
例子
因为thread类比较简单,我们通过几个例子来学习。
支持移动语义,但不支持拷贝语义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void some_function() {}
void some_other_function() {}
int main()
{
std::thread t1(some_function); // 构造一个thread对象t1
std::thread t2 = std::move(t1); // 把t1 move给另外一个thread对象t2,t1不再管理之前的线程了。
// 这句不需要std::move(),从临时变量进行移动是自动和隐式的。调用的是operator=(std::thread&&)
t1 = std::thread(some_other_function);
std::thread t3;
t3 = std::move(t2); // 把t2 move给t3
// 把t3 move给t1,非法。因为`t1`已经有了一个相关的线程,会调用`std::terminate()`来终止程序。
t1 = std::move(t3);
}通过调用join()成员函数来等待线程结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::cout << "starting first helper...\n";
std::thread helper1(foo);
helper1.join();
}传递参数给线程函数
1
2void f(int i, const std::string& s);
std::thread t(f, 3, "hello");
注意:参数会以默认的方式被复制到内部存储空间,直到使用的时候才会转成对应的类型。
下面的例子有问题吗?有什么问题?
1
2
3
4
5
6
7
8void f(int i, const std::string& s);
void oops(int some_param)
{
char buffer[1024];
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, buffer);
t.detach();
}局部变量
buffer
的指针会被传递给新线程,并在新线程里转换成string
,如果oops()
在buffer
被转换成string
之前退出,那么会导致未定义的行为(因为buffer
是局部变量,oops()
结束后,buffer
对应的内存不能被使用了)。解决之道是在构造std::thread
的时候传递string
变量。std::thread t(f, 3, std::string(buffer));
可以使用
std::ref()
来显示表明要传递引用,就像std::bind()
那样。1
2
3std::string data;
void test(int a, std::string& data) { data = "modified"; }
std::thread t(test, w, std::ref(data));使用类的成员函数作为线程参数
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
class CRunner
{
public:
void run0(){}
void run1(int a) {}
void run2(int a, int b) const {}
int run3(int a, char b, const std::string& c) {return 0;}
int run4(int& a, double b, float c, char d) { ++a; return 0; }
static void run_static(int a) {}
};
int main()
{
CRunner runner;
int a = 0;
// 使用std::mem_fun,需要传指针
std::thread t0(std::mem_fun(&CRunner::run0), &runner);
// 使用std::mem_fun_ref,可以传引用或副本
std::thread t1(std::mem_fun_ref(&CRunner::run1), std::ref(runner), 1);
// std::thread t1(std::mem_fun_ref(&CRunner::run1), runner, 1);
// 使用std::mem_fn,std::mem_fn支持多于一个参数的函数,std::mem_fun不支持。
std::thread t2(std::mem_fn(&CRunner::run2), std::ref(runner), 1, 2);
// 使用std::bind + std::mem_fn
std::thread t3(std::bind(std::mem_fn(&CRunner::run3), &runner, 1, 2, "data"));
// 使用std::mem_fn,注意std::ref的用法,如果不用std::ref行不行?
std::thread t4(std::mem_fn(&CRunner::run4), &runner, std::ref(a), 2.2, 3.3f, 'd');
// 使用类的静态函数
std::thread t5(&CRunner::run_static, 1);
t0.join();
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}
注意
- std::mem_fun需要与类指针配合,std::mem_fun_ref可以和类引用或类副本配合。
- std::mem_fun或std::mem_fun_ref只支持最多一个参数的可调用对象(函数,仿函数等),在新标准中已经废弃不用了。
- std::mem_fn是std::mem_fun的增强版,不需要区分传递指针或者传递引用。而且可以支持传递多个参数。
- std::thread只需要一个可以调用的对象(函数,仿函数等),所以我们也可以通过std::bind()的返回值来作为std::thread的参数。
更多
虽然在之前的例子中的函数有返回值,但是我们却不能获得,想获得返回值我们需要使用std::future,关于std::future的总结,后续会慢慢补充,敬请期待。
参考资料
- 《C++并发编程实战》
- cppreference