公众号「编程学习基地」

文章目录

  • ​​1.启动线程​​
  • ​​2.等待线程完成​​
  • ​​2.1特殊情况下的等待​​
  • ​​2.2使用RAII等待线程完成​​
  • ​​2.3后台运行线程​​
  • ​​2.4量产线程,等待结束​​
  • ​​2.传递参数​​
  • ​​3.转移线程所有权​​
  • ​​4.运行时决定线程数量​​
  • ​​2.5 识别线程​​

1.启动线程

当把函数对象传入到线程构造函数中时,需要避免语法解析问题,不能直接传入函数,而是需要传入函数地址

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}

class workThread{
public:
workThread(){}
void operator()()
{
cout<<"[2]thread Id:"<<std::this_thread::get_id()<<endl;
}
};

int main(){
thread thr(thread_fun);
workThread workthr2;
/**
* @brief 线程类启动线程的三种方式
*/
// thread thr2(workthr2);
// thread thr2((workThread()));
thread thr2{workThread()};
thr2.join();
thr.join();
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
return 0;
}

2.等待线程完成

​std::thread​​实例需要使用join(),可以确保局部变量在线程完成后被销毁。join()是简单粗暴的等待线程完成或不等待。只能对一个线程使用一次join();一旦已经使用过join(),​​std::thread​​对象就不能再次加入了,当对其使用joinable()时,将返回否false)。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
thread thr(thread_fun);
if(thr.joinable())
{
thr.join();
}
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
return 0;
}

2.1特殊情况下的等待

需要对一个还未销毁的​​std::thread​​对象使用join()或detach()。如果想要分离一个线程,可以在线程启动后,直接使用**detach()**进行分离。如果打算等待对应线程,需要选择一个合适的位置。例如当出现异常并抛出异常后未正确调用线程等待,需要在异常处理过程中调用异常。

void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func); //开启线程
try
{
do_something_in_current_thread(); //执行其他的一些任务
}
catch(...) //执行其他任务时出现异常捕获后抛出异常前需要等待线程结束
{
t.join(); // 1
throw;
}
t.join(); // 2
}

可看可不看,不重要…

2.2使用RAII等待线程完成

RAII是Resource Acquisition Is Initialization(wiki上面翻译成 “资源获取就是初始化”)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。

#include<iostream>
#include<thread>
using namespace std;
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_)
{}
~thread_guard()
{
if(t.joinable())
{
t.join();
}
}
thread_guard(thread_guard const&)=delete;
thread_guard& operator=(thread_guard const&)=delete;
};
void thread_fun(){
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
thread thr(thread_fun);
thread_guard guard(thr);
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
return 0;
}

拷贝构造函数和拷贝赋值操作被标记为​​=delete​​,是为了不让编译器自动生成它们。直接对一个对象进行拷贝或赋值是危险的,因为这可能会弄丢已经加入的线程。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。

2.3后台运行线程

使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有​​std::thread​​对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。

通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,且没有任何用户接口,并在后台运行的线程。

std::thread t(do_background_work);
t.detach();
assert(!t.joinable());

2.4量产线程,等待结束

通过标准中的​​std::vector<>​​量产线程并且等待他们结束

#include <iostream>
#include <numeric>
#include <vector>
#include <thread>
#include <functional> //mem_fn
#include <algorithm> //for_each
using namespace std;
template<typename Iterator,typename T>
struct accumulate_block
{
void operator()(Iterator first,Iterator last,T& result)
{
result=std::accumulate(first,last,result);
}
};
struct MyAccumulate
{
void operator()(vector<int>::iterator begin,vector<int>::iterator end,int& result)
{
result=std::accumulate(begin,end,result);
}
};
void fun(vector<int>::iterator begin,vector<int>::iterator end,int& result){
result = std::accumulate(begin, end, result);
}
int main(){
//定义完成任务的线程数量
int thread_num = 4;
vector<int> vec;
//任务:有一个100大小容器,需要统计容器中数量的和
for(int i=0;i<100;i++)
vec.push_back(i + 1);
//计算每个线程需要计算的快大小
int block_size = 100 / thread_num;
//统计任务结果集,每个线程统计一个结果
vector<int> results(thread_num);
vector<thread> threads(thread_num);
vector<int>::iterator block_begin = vec.begin();
for(int i=0;i<thread_num;i++){
vector<int>::iterator block_end = block_begin;
//将迭代器往后移动 block_size 大小
std::advance(block_end,block_size);
MyAccumulate tmp;
// threads[i] = std::thread(accumulate_block<vector<int>::iterator,int>(),block_begin,block_end,std::ref(results[i]));
threads[i] = std::thread(fun, block_begin, block_end, std::ref(results[i]));
block_begin = block_end;
}
for_each(threads.begin(),threads.end(),std::mem_fn(&thread::join));
int result = std::accumulate(results.begin(), results.end(), 0);
cout << "result:" << result <<endl;
}

该示例主要是演示创建多个线程分配任务后对计算结果进行统计。

2.传递参数

因为线程的创建属于函数式编程,所以传递的参数都会被拷贝一份,传入参数时尽可能传入指针

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(int* arg){
int tmp = *(int*)arg;
cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<tmp << endl;
}
class workThread{
int m_val;
public:
workThread(int val){
m_val = val;
}
void operator()()
{
cout<<"thread Id:"<<std::this_thread::get_id()<<","<<m_val<<endl;
}
};
int main(){
int arg = 10;
thread thr(thread_fun,&arg);
thr.join();
workThread workthr2(arg);
// std::thread thr2(workthr2);
// std::thread thr2((workThread(arg)));
std::thread thr2{(workThread(arg))};
thr2.join();
return 0;
}

当需要传递引用时,构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量,可以使用​​std::ref​​将参数转换成引用的形式。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun2(int &arg){
arg = 20;
cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
}
int main(){
int arg = 10;
thread thr(thread_fun2,std::ref(arg));
thr.join();
cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
return 0;
}
thread Id:139977403291392,tmp:20
thread Id:139977420166976,arg:20

3.转移线程所有权

C++标准库中有很多资源占有(resource-owning)类型,比如​​std::ifstream​​​,​​std::unique_ptr​​​还有​​std::thread​​都是可移动(movable),但不可拷贝(cpoyable)。这就说明执行线程的所有权可以在​​std::thread​​实例中移动。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun2(int &arg){
arg = 20;
cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
}
int main(){
int arg = 10;
thread thr(thread_fun2,std::ref(arg));
thread thr2 = std::move(thr);
if(thr2.joinable()){
cout<<"thr2 joinable.."<<endl;
thr2.join();
}
cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
return 0;
}

当所有权可以在函数内部传递,就允许​​std::thread​​​实例可作为参数进行传递,​​std::thread​​支持移动的好处是可以创建thread_guard类的实例,并且拥有其线程的所有权。

为了确保线程程序退出前完成,下面的代码里定义了thread_guard类

#include <iostream>
#include <thread>
using namespace std;
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_)
{
if(!t.joinable())
throw std::logic_error("No thread");
}
~thread_guard()
{
t.join();
}
thread_guard(thread_guard const&)=delete;
thread_guard& operator=(thread_guard const&)=delete;
};
void thread_fun(){
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}
int main(){
thread thr(thread_fun);
thread_guard guard(thr);
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
return 0;
}

4.运行时决定线程数量

​std::thread::hardware_concurrency()​​在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。但是,这也无法掩盖这个函数对启动线程数量的帮助。

#include <thread>
#include <numeric>
#include <algorithm>
#include <functional>
#include <vector>
#include <iostream>
using namespace std;

template<typename Iterator,typename T>
struct accumulate_block
{
void operator()(Iterator first,Iterator last,T& result)
{
result=std::accumulate(first,last,result);
}
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
unsigned long const length=std::distance(first,last); //该函数会返回[first, last)范围内包含的元素的个数

if(!length)
return init;

unsigned long const min_per_thread=25; //定义每个线程计算的任务量
unsigned long const max_threads= //通过每个线程计算的任务量和总任务计算所需线程数量,-1的原因是保证最少需要一个任务才能启动一个线程
(length+min_per_thread-1)/min_per_thread;
cout<<"max thread:"<<max_threads<<endl;
unsigned long const hardware_threads=
std::thread::hardware_concurrency(); //返回能同时并发在一个程序中的线程数量,例如,多核系统中,返回值可以是CPU核芯的数量。

cout<<"hardware_threads:"<<hardware_threads<<endl;
unsigned long const num_threads=
std::min(hardware_threads!=0?hardware_threads:2,max_threads);
cout<<"num_threads:"<<num_threads<<endl;
unsigned long const block_size=length/num_threads;

std::vector<T> results(num_threads);
std::vector<std::thread> threads(num_threads-1); //线程数量减一的原因是当前线程也参与计算,

Iterator block_start=first;
for(unsigned long i=0;i<(num_threads-1);++i)
{
Iterator block_end=block_start;
//将迭代器往后移动 block_size 大小
std::advance(block_end,block_size);
threads[i]=std::thread(
accumulate_block<Iterator,T>(),
block_start,block_end,std::ref(results[i]));
block_start=block_end;
}
//当前线程也参与计算部分任务量
accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]);

std::for_each(threads.begin(),threads.end(),
std::mem_fn(&std::thread::join));

return std::accumulate(results.begin(),results.end(),init);
}

int main()
{
std::vector<int> vi;
for(int i=0;i<100;++i)
{
vi.push_back(10);
}
int sum=parallel_accumulate(vi.begin(),vi.end(),5);
std::cout<<"sum="<<sum<<std::endl;
}
[root@zs3-2 lesson1]# ./main
max thread:4
hardware_threads:32
num_threads:4
sum=1005

这个demo有点苦涩难懂,多花点时间,结合​​2.4量产线程,等待线程结束​​一起思考更好。

2.5 识别线程

线程标识类型是​​std::thread::id​​​,可以通过两种方式进行检索。第一种,可以通过调用​​std::thread​​​对象的成员函数​​get_id()​​​来直接获取,注意一定要先有​​std::thread​​​对象哦。第二种,当前线程中调用​​std::this_thread::get_id()​​​(这个函数定义在​​<thread>​​头文件中)也可以获得线程标识。

#include<iostream>
#include<thread>
using namespace std;
void thread_fun(){
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
}

int main(){
thread thr(thread_fun);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
cout<<"thr id:"<<thr.get_id()<<endl;
thr.join();
cout<<"thr id:"<<thr.get_id()<<endl; //返回std::thread::type默认构造值,这个值表示“没有线程”
cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
return 0;
}