C++并发编程 2:线程管理
线程的创建
线程通过构建std::thread
对象而启动,std::thread
的构造函数:
// 默认构造
std::thread t1;
// 使用任意可调用类型构造
std::thread t2(F&& f, Args&&... args);
// 使用另外一个线程移动构造
std::thread t3(std::move(other_thread));
可调用类型构造
普通函数
int add(int a, int b) { return a + b; }
std::thread t(add, 1, 2);
函数指针
int add(int a, int b) {return a + b; }
int (*p_add)(int , int ) = add;
std::thread t(p_add, 1, 2);
类成员函数
若要某个类的成员函数设为线程函数,则需要将成员函数指针和对象实例地址一起传递给std::thread
构造函数。
class Foo {
public:
int add(int a, int b) { return a + b; }
};
Foo foo;
std::thread t(&Foo::add, &foo, 1, 2);
lambda函数
std::thread t([](int a, int b) { return a + b; }, 1, 2);
重载函数调用符的类对象
class Functor {
public:
int operator()(int a, int b) { return a + b; }
};
Functor f;
std::thread t(f, 1, 2);
如果f为参数列表,且传入临时变量:
class Functor {
public:
int operator()() { std::cout << "hello" << std::endl; }
};
此时,使用std::thread t(Functor());
则会被解释为:声明了一个名为t
,返回值为std::thread
,函数参数为一个函数指针的函数。
使用列表初始化方式可避免这种情况:std::thread t{Functor()};
std::function
std::function
是一个函数对象类,它包装其它任意的函数对象:
std::function<int(int, int)> f = [](int a, int b) { return a + b; };
std::thread t(f, 1, 2);
向线程函数传递参数
线程具有内部存储空间,在传参到线程函数时:
- 参数按照默认方式复制,这些副本被当成临时变量。
- 进入到新线程的上下文环境后,以右值的形式传给新线程上的函数或可调用对象。
参数避免隐式转换
void f(int i, std::string const& s);
char buffer[256];
std::thread t(f, 3, buffer);
t.detach();
上述例子中,buffer
从char*
隐式转换为std::string
的操作是在新线程中进行,但此时主线程可能已经销毁,buffer
已经析构,所以可能会出现未定义的行为。
传递参数的引用
如上所述,参数在传递给新线程时,会传递参数的副本
class Foo { ... };
void f(int i, Foo& f);
Foo foo;
std::thread(3, foo);
在上例中,即使函数的参数为引用类型,但引用类型会被忽略,最终还是传递了参数的拷贝。 被拷贝的参数会被当成move-only类型,并以右值的形式传递。
如何解决? 使用std::ref
进行包装,std::ref
用来构建一个std::reference_wrapper
对象并返回,该对象拥有传入变量的引用:
std::thread(3, std::ref(foo));
移动构造
std::thread支持移动语义的意义是:可以向外部移交线程的归属权。
std::thread t1([](int a, int b) { return a + b; }, 1, 2);
std::thread t2 = std::move(t1);
不能赋值构造
std::thread
的赋值构造函数被定义为delete
,无法使用赋值语句将一个线程对象赋值给另一个线程对象。
thread(const thread&) = delete;
join
join()
阻塞创建新线程的线程,直到新线程执行完毕。
此时新线程和创建新线程的线程汇合(join)。
调用join()
的时机:在创建新线程的线程结束之前。
调用join()
之前,必须使用joinable()
判断线程是否joinable,如果一个线程曾经调用过join()
,则该线程不再joinable。
detach
detach()
将新线程从创建它的线程分离开来,允许新线程在后台运行。
此时新线程的归属权和控制器移交给C++ runtime library。
只用当joinable()
返回true
时,才能调用detach()
。
获取线程数量
std::thread::hardware_concurrency()
返回硬件真正并发的线程数量。
int num_threads = std::thread::hardware_concurrency();
std::vector<std::thread> threads;
auto f = [](int a, int b) { return a + b; };
for(int i = 0; i < num_threads; ++i) {
threads.emplace_back(f, 1, 2);
}
识别线程
通过线程ID来识别线程,线程ID的类型为std::thread::id
std::this_thread::get_id()
返回当前线程的ID。std::thread
的成员函数get_id()
返回线程对象的ID。