一、线程对象如何获得?
线程对象的获取,可以分为以下几种情况:
- case1 无中生有,通过传递函数对象+参数的方法完成一个线程的构造
- case2 假借他人,通过默认构造函数并通过移动构造完成线程的创建
所以,我们标准库有以下的构造函数定义:
thread() noexcept;//since c++11
thread(thread&& other) noexcept;//since c++11
template<class Function,class ...Args>
explicit thread(Function &&f,Args && ...args);//since c++11
thread(const thread&)=delete;//since c++11
- 第一个是默认构造,构造一个什么也不做的线程对象;
- 第二个是std::move()会调用的移动构造函数;
- 第三个是类模板,表示使用一个函数对象和若干个参数生成对应的线程类;
注意:线程不能拷贝,但是可以移动。移动之前应该保证移动对象是处于joinable状态,如果你默认构造一个线程,总是可以移动的,因为默认是joinable的
第三个构造函数是构造一个有实际还行意义的唯一方法,其模板参数如下:
- 一个函数对象
- 零个或者多个参数
参数很好理解,那么函数对象是什么,都有哪些种类呢?
二、函数对象
函数对象std::function
是C++标准库定义的通用多态包装器,std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标:
- 普通函数
- lambda表达式
- bind表达式
- 其他可调用对象(如仿函数)
- 指向成员函数指针
- 成员数据是指向函数的指针
存储的可调用对象称为std::function
对象的目标,如果目标不存在,称函数对象为空,调用它将会抛出std::bad_function_call
错误。简单来说,函数对象类是存储函数对象的一个容器。
上面列举的可调用目标都可以用统一的容器std::function
封装。那么他都有哪些构造函数呢?
function() noexcept; //(1) (C++11 起)
function( std::nullptr_t ) noexcept;//(2) (C++11 起)
function( const function& other ); //(3) (C++11 起)
function( function&& other ); //(4) (C++11 起,C20止)
第二个构造函数是最常用的,构造的参数是一个std::nullptr_t
非空指针类型,也就是说:
- lambada
- 普通函数
- 指向函数成员的指针
- 数据成员是指针
都可以完成函数对象的构造!
注意:
- 线程函数参数要么是拷贝,要么是移动。如果是引用参数,请使用
std::ref
或者std::cref
包裹后传入。 - 线程的返回值将会被忽略。如果一个函数抛出一个异常,将会调用
std::terminate
,如果你确实需要返回一个值或者异常给调用这个线程的线程,那么你可以使用std::promise
或者std::async
。
三、例子
实例一:创建两个线程分别打印奇数和偶数
#include <iostream>
#include <thread>
using namespace std;
void show_even()
{
for(int i=0;i<10;i+=2)
{
cout<<i;
}
};
void show_odd()
{
for(int i=1;i<10;i+=2)
{
cout<<i;
}
}
int main()
{
thread th1(show_odd);
thread th2(show_even);
th1.join();
th2.join();
return 0;
}
每次运行的结果都不一样,任取三次结果:0246813579
1357902468
1350247968
。cpu每次只会在一个线程中执行,如果处理的速度足够简单,可能一个线程所有内容完成了,另一个线程还没开始。
对于一个简单的执行内容,使用lambda创建一个可调用对象更为合适:
int main()
{
thread th1([](){for(int i=0;i<10;i+=2){cout<<i;}});
thread th2([](){for(int i=1;i<10;i+=2){cout<<i;}});
th1.join();
th2.join();
return 0;
}
不用想函数名,不用专门写一个函数,blabla.
实例2:类的成员函数
class A
{
public:
void printA(){std::cout<<"A"<<std::endl;}
void showID(){std::cout<<"A's ID"<<std::endl;}
};
int main()
{
A a;//首先需要有一个类的实例
std::thread th1(&A::printA,&a);//第一个参数是类中地址,第二个参数是类实例地址
a.showID();
th1.join();
}
注意,上面这个例子只是printA
函数中的语句泡在新的子线程中,showID
因为调用时在主线程,所以其仍在主线程。
实例3:传递一个参数(引用和非引用)
class A
{
public:
void printA(){std::cout<<"A"<<std::endl;}
void doublePassedValue(int & value){value*=2;}
void showID(){std::cout<<"A's ID"<<std::endl;}
void addTwoPassedValue(int value){value+=2;}
private:
int m_id;
};
int main()
{
int value=2;
A a;//首先需要有一个类的实例
std::thread th1(&A::doublePassedValue,&a,std::ref(value));//ok!用std::ref进行了包裹
std::thread th1(&A::doublePassedValue,&a,value);//error
std::thread th1(&A::addTwoPassedValue,&a,value);//ok!参数是值传递,不需要用std::ref
th1.join();
}
20211022 更新了一些文字描述,增加了两个例子
20211130 增加了std::function的描述