多线程的基本用法(C++11)原创
# 多线程的基本用法(C++11)
# 介绍
C++11标准第一次承认多线程在语言中的存在,并在标准库中为多线程提供组件。
# 启动新线程
# 包含头文件
#include <thread>
# 构造 std::thread 对象
使用C++线程库启动线程, 可以归结为构造 std::thread 对象。
void do_some_work();
std::thread my_thread(do_some_work);
2
如同大多数 C++标准库一样, std::thread 可以用可调用类型构造,将带有函数调用符类型的实例传 入 std::thread 类中,替换默认的构造函数。
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f); // 1 正确:使用命名函数对象的方式
// std::thread my_thread(background_task()); // 2 错误:background_task()会被当作函数声明传到thread中
std::thread my_thread((background_task())); // 3 正确:或使用多组括号
std::thread my_thread{background_task()}; // 4 正确:使用新统一的初始化语法
2
3
4
5
6
7
8
9
10
11
12
13
14
使用lambda表达式也能避免这个问题。lambda表达式是C++11的一个新特性,它允许使用一个可以捕获局部变量的局部函数之前的例子可以改写为lambda表达式的类型:
std::thread my_thread([]{
do_something();
do_something_else();
});
2
3
4
# 等待线程与分离线程
启动了线程,你需要明确是要等待线程结束(加入式),还是让其自主运行(分离式)。
join()
函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束。当调用join()
,主线程等待子线程执行完之后,主线程才可以继续执行,此时主线程会释放掉执行完后的子线程资源。
detach()
称为分离线程函数,使用detach()
会让线程在后台运行,这就意味着主线程不能与之产生直接交互。如果线程分离,那么就不可能有 std::thread 对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。不过C++运行库保证,当线程退出时,相关资源的能够正确回收,后台线程的归属和控制C++运行库都会处理。
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
2
3
# 向线程函数传递参数
# 普通函数作为线程函数
向 std::thread 构造函数中的可调用对象,或函数传递一个参数很简单。需要注意的是,默认参数要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问。
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
2
函数f需要一个std::string
对象作为第二个参数,但这里使用的是字符串的字面值,也就是 char const *
类型。之后,在线程的上下文中完成字面值向 std::string 对象的转化。
期望传递一个引用,但整个对象被复制了。当线程更新一个引用传递的数据结构时,这种情况就可能发生,可以使用std::ref
将参数转换成引用的形式,从而可将线程的调用改为以下形式:
void f(int i, std::string& s)
{
// 线程中对s有修改
s = "hello world";
}
std::string s("hello");
std::thread t(f, 3, std::ref(s));
2
3
4
5
6
7
# 类的成员函数作为线程函数
class X
{
public:
void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num)
2
3
4
5
6
7
8
# Lambda表达式/匿名函数作为线程函数
std::thread t{
[] {std::cout << "线程函数被启动" << std::endl; }
};
2
3
# 转移线程所有权
C++标准库中有很多资源占有(resource-owning)类型, 比如std::ifstream
,std::unique_ptr
还有 std::thread
都是可移动,但不可拷贝。这就说明执行线程的所有权可以在 std::thread
实例中移动,下面创建了两个执行线程,并且在std::thread
实例之间(t1,t2和t3)转移所有权:
void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3
std::thread t3; // 4
t3=std::move(t2); // 5
t1=std::move(t3); // 6 赋值操作将使程序崩溃
2
3
4
5
6
7
8
# 线程唯一标识符
线程标识类型是 std::thread::id
,可以通过两种方式进行检索。第一种,可以通过调用 std::thread
对象的成员函数get_id()
来直接获取。如果 std::thread
对象没有与任何执行线程相关联, get_id()
将返回 std::thread::type
默认构造值,这个值表示“无线程”。第二种,当前线程中调用 std::this_thread::get_id()
(这个函数定义在头文件中)也可以获得线程标识。
std::thread::id
对象可以自由的拷贝和对比,因为标识符就可以复用。如果两个对象的 std::thread::id
相等,那它们就是同一个线程,或者都“无线程”。如果不等,那么就代表了两个不同线程,或者一个有线程,另一没有线程。
std::thread::id
实例常用作检测线程是否需要进行一些操作,比如:当用线程来分割一项工作,主线程可能要做一些与其他线程不同的工作。这种情况下,启动其他线程前,它可以将自己的线程ID通过std::this_thread::get_id()
得到,并进行存储。就是算法核心部分(所有线程都一样的),每个线程都要检查一下,其拥有的线程ID是否与初始线程的ID相同。
std::thread::id master_thread;
void some_core_part_of_algorithm()
{
if(std::this_thread::get_id()==master_thread)
{
do_master_thread_work();
}
do_common_work();
}
2
3
4
5
6
7
8
9
# 简单案例
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
typedef void (*Callback)(int &id);
mutex some_mutex;
void notifyDone(int &id) {
id = 1;
}
void fun1(Callback callback,int&id) {
int i = 0;
while (1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::lock_guard<std::mutex> guard(some_mutex);
cout << "thread1 running, i:" << i++ << endl;
if( i > 10 ) break;
}
cout << "thread1 end." << endl;
callback(id);
}
void fun2(Callback callback,int&id) {
int i = 0;
while (1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::lock_guard<std::mutex> guard(some_mutex);
cout << "thread2 running, i:" << i++ << endl;
if( i > 10 ) break;
}
cout << "thread2 end." << endl;
callback(id);
}
int main() {
Callback callback;
callback=notifyDone;
int flag1=0;
int flag2=0;
thread t1(fun1,callback,ref(flag1));
thread t2(fun2,callback,ref(flag2));
while (1) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::lock_guard<std::mutex> guard(some_mutex);
cout << "joinable? fun1:" << t1.joinable() << " fun2:"<< t2.joinable() <<",flag1:"<<flag1<<",flag2:"<<flag2<< endl;
}
return 0;
}
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
- 01
- Linux系统移植(五)--- 制作、烧录镜像并启动Linux02-05
- 03
- Linux系统移植(三)--- Linux kernel移植02-05