多线程库
C++11
中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。
多线程库对应的头文件是#include <thread>
,类名为std::thread
。
串行程序:
#include <iostream>
#include <thread>
void function_1()
{
std::cout << "I‘m function_1()" << std::endl;
}
int main()
{
function_1();
return 0;
}
这是一个典型的单线程的单进程程序,任何程序都是一个进程,main()
函数就是其中的主线程,单个线程都是顺序执行。
将上面的程序改造成多线程程序,让function_1()
函数在另外的线程中执行:
#include <iostream>
#include <thread>
void function_1()
{
std::cout << "I‘m function_1()" << std::endl;
}
int main()
{
std::thread t1(function_1);
// do other things
t1.join();
return 0;
}
1. 构建一个std::thread类的
对象t1
,构造的时候传递了一个参数,这个参数是一个函数,这个函数就是这个线程的入口函数,函数执行完整个线程也就执行完了;
2. 线程创建成功后,就会立即启动;
3. 一旦线程开始运行, 就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。需要在std::thread
对象被销毁之前做出决定;
本例选择了使用t1.join()
,主线程(main函数)会一直阻塞,直到子线程(function_1函数)完成,join()
函数的另一个任务是回收该线程中使用的资源;
我们也可以调用t1.detach()
,从而将子线程放在后台运行,所有权和控制权被转交给C++
运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。被分离的线程被称为守护线程(daemon threads)。线程被分离之后,即使该线程对象被析构了,线程还是能够在后台运行,只是由于对象被析构了,主线程不能够通过对象名与这个线程进行通信。线程对象(t1)和对象内部管理的线程(子线程)的生命周期并不一样;如果线程执行的快,可能内部的线程已经结束了,但是线程对象还活着;也有可能线程对象已经被析构了,内部的线程还在运行。
#include <iostream>
#include <thread>
void function_1()
{
//延时500ms 为了保证test()运行结束之后才打印
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "I‘m function_1()" << std::endl;
}
void test()
{
std::thread t1(function_1);
t1.detach();
// t1.join();
std::cout << "test() finished" << std::endl;
}
int main()
{
test();
//让主线程晚于子线程结束
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //延时1s
return 0;
}
// 使用 t1.detach()时
// test() finished
// I‘m function_1()
// 使用 t1.join()时
// I‘m function_1()
// test() finished
1. 由于线程入口函数内部有个500ms
的延时,所以在还没有打印的时候,test()
已经执行完成了,t1
已经被析构了,但是它负责的那个线程还是能够运行,这就是detach()
的作用;
2. 如果去掉main
函数中的1s
延时,会发现只打印了test() finished,因为主线程执行的太快,整个程序已经结束了,后台线程被C++
运行时库回收了;
3. 如果将t1.detach()
换成t1.join()
,test
函数会在t1
线程执行结束之后,才会执行结束。
一旦一个线程被分离了,就不能够再被join
了。如果非要调用,程序就会崩溃,可以使用joinable()
函数判断一个线程对象能否调用join()
void test()
{
std::thread t1(function_1);
t1.detach();
if(t1.joinable())
t1.join();
assert(!t1.joinable());
}
线程类的构造函数
std::thread
类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数;第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数。第一个参数是一个可调用对象(Callable Objects),可以是以下几种情况:
operator()
运算符的类对象,即仿函数lambda
表达式(匿名函数)std::function
// 普通函数 无参
void function_1()
{}
// 普通函数 1个参数
void function_2(int i)
{}
// 普通函数 2个参数
void function_3(int i, std::string m)
{}
std::thread t1(function_1);
std::thread t2(function_2, 1);
std::thread t3(function_3, 1, "hello");
t1.join();
t2.join();
t3.join();
// 仿函数
class Fctor
{
public:
void operator() ()
{}
};
Fctor f;
std::thread t4{Fctor()}; //注意加花括号
f
,当使用f()
时就调用operator()
运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。//[]捕获列表,用于捕获上下文变量供lambda使用
//同时,编译器根据该符号可以判断接下来是lambda函数
//()参数列表,无参数的话可以省略
//返回值类型
//{}函数体
std::thread t1( [](){std::cout << "hello" << std::endl;} );
//“world”为参数m的值
std::thread t2([](std::string m)
{std::cout << "hello " << m << std::endl;}, "world");
class A
{
public:
void func1()
{}
void func2(int i)
{}
void func3(int i, int j)
{}
};
A a;
//std::function<返回值类型(参数类型)>
//std::bind(函数对象,参数)
std::function<void(void)> f1 = std::bind(&A::func1, &a);
std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);
std::thread t1(f1);
std::thread t2(f2);
std::thread t3(f3, 1);
std::thread t4(f4, 1);
std::thread t5(f5, 1, 2);
原文:https://www.cnblogs.com/yongjin-hou/p/14527220.html