c++多线程(一)线程管理

来源:微信公众号「编程学习基地」

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量产线程,等待线程结束一起思考更好。

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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeRoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值