C++线程

C++11后可以通过提供的线程类thread进行线程创建,实现线程跨平台

#include <iostream>
#include <thread>    //包含线程头文件
using namespace std;

//线程要执行的任务,或者说入口
void myprint(int a)
{
    cout << a << "线程开始执行" << endl;
}

class myclass
{
public:
    bool greater(int a,int b)
    {
    return a > b;
    }
    void myprint(int a)
    {
     cout << a << "线程开始执行" << endl;
    }
 void operator() (int a) 
    {
      cout << a << "线程开始执行" << endl;
    }
};

int main()
{
    //通过实例化thread构建线程并开始执行,第一个参数为可执行对象
    //可以是普通函数,函数指针,仿函数,lambda表达式,使用bind构造的可调用对象
    //之后的参数是线程入口函数的参数
    
    //普通函数做线程入口函数,参数2
    thread mythread(myprint,2);  
    
    //lambda表达式做线程入口函数,参数10
    thread mythread2([](int p){
        cout << p << "线程开始执行" << endl;   
    },10);                     
    
    //类成员函数做线程入口函数,参数3
    myclass m;
                       //函数         类对象 参数 
    thread mythread3(&myclass::myprint, &m ,3);  //使用m的myprint函数,也可以ref(m)
    thread mythread3(&myclass::myprint, m ,3);  //线程复制一个myclass对象,调用了它的myprint
    //通过bind将类成员函数转成可调用对象
    thread mythread3(bind(&myclass::myprint, &m, placeholders::_1),3);  
    
    //仿函数做线程入口函数,参数6
    thread mythread4(m ,6);    //主线程调用拷贝构造函数复制一个myclass对象到子线程
    thread mythread4(ref(m) ,6); //使用主线程局部变量m
    
    //线程创建完成后开始执行,我们还需要指定其与主线程的先后关系
    //线程join()使得主线程要等待子线程完成后才能继续执行,是同步关系
    //线程detach()使主线程不等待子线程,可能造成主线程执行完但子线程还没执行完
    //这时候主线程的局部变量可能失效了,导致不可预料的后果!需要谨慎使用
    mythread.join(); 
    mythread2.join(); 
    mythread3.join();
    mythread4.join();
    //join和detach只能有一个,且必须指定一个
    
    //线程还有几个比较重要的函数
    mythread.get_id(); //获取线程id
    mythread.swap(mythread2);  //交换两个线程的执行内容
    mythread.joinable(); //检查线程是否可以被join或detach
}

使用detach时需要避免的问题

1.函数不使用指针做参数

2.函数使用引用作为参数,但是线程默认还是值传递,除非使用ref指定才能进行真引用

void myprint(string &s)  //真引用
{                        
    s+="aaa";
     cout << s << endl;
}

int main()
{
    string a = "abc",b="bcd"; 
    thread mythread(myprint,ref(a)); //真引用
    thread mythread2(myprint,b);  //还是值传递 
    mythread.detach();
    mythread2.detach();
}

3.使用string& 接收主线程的 char指针指向的数据 可能会有主线程执行完,但是char指针的数据还没有转成string参数,导致出错

void myprint(const string &s)  //要用引用接收,不然系统多一次拷贝构造,(变成两次拷贝构造)
{                        //虽然用了引用,但系统默认会用值传递,所以传参时调用拷贝构造
    cout << s << endl;
}
int main()
{
    char a[] = "abc"; 
   // thread mythread(myprint,a);  //a转string是在子线程执行的,编译时看到这一行只压栈
    //主线程调用string构造函数
    thread mythread(myprint,string(a)); //编译时要先将a转string,此时会顺便调用拷贝构造,形成myprint的实参,压栈,不会发生要转换时已经失效
    mythread.detach();
}

除了thread外,还有另外一个async,这个是创建异步任务。

std::async配合std::future使用

async创建异步任务,用法和thread类似,返回值用future接收,这个future的值在未来某时间可以使用get()成员函数获取 (线程执行完毕时)

第一个参数是启动方式,使用launch::async ,代表创建异步任务并开始执行(即 开一个子线程,并执行线程入口函数) ;另一个启动方式是launch::deferred,代表延迟调用,使得某个线程在调用future的get()或wait()成员时,执行线程入口函数。

如果启动方式缺省,那么行为是由系统决定的,可能是async方式,也可能是deferred

相当于用 launch::async | launch::deferred (一般在资源足够时自动用async,不够时用deferred)

第二个及后续参数是可调用对象和所传的参数,同thread

#include<future>
int func (int a)
{
    cout << a << endl;
    cout << "thread_id=" << this_thread::get_id() << endl;
}

int main()
{
    future<int> result = async(launch::async,func,3);    //创建异步任务并开始执行
    future<int> result1 = async(launch::deferred,[]{
        return 10;    
    });                       //创建异步任务,延迟执行

    //future<int> result2 = async(func,3); 行为未知
    // == future<int> result2 = async(launch::async | launch::deferred,func,3);
    
    //async的成员函数get()获取异步任务执行结果(返回值),如果已经执行完就直接获取,
    //不然就在此行阻塞等待任务完成再获取  只能执行一次get  get后不能再wait或get
    //因为get的设计是一个移动语义move,future保存的数据被move走了
    //如果需要多次使用,可以考虑shared_future,后文会提到 
   
    //wait()成员函数阻塞等待任务执行完成,但不获取结果 
    //如果是以async方式启动,那么主线程执行完也会自动等待异步任务完成,最后再退出
    //deferred方式如果没有wait或get那么就不执行

    result.wait();
    cout << result1.get();
}

如果使用缺省或 launch::async | launch::deferred 方式调用async,那么编程时需要考虑两种方式下该怎么执行。

future的wait_for函数返回了三种状态,可以通过这个状态判断async是哪种执行方式,相应做出不同操作,后面会介绍这三种

void func()
{
    
}

int main()
{
    future<int> result = async(func);
    future_status s = result.wait_for(chrono::seconds(0));   //wait_for(0s)也是可以的写法
    if(s == future_status::deferred )  //deferred方式,且还没有开始执行
    {
       result.wait();//或者get   //在此线程开始执行async创建的任务
    }
    else   //执行完成或者还在执行
    {
         //....   
    }
}

什么时候用async

使用thread会直接开始执行,执行完就销毁线程,下次再调用大概率是创建新线程。其接收返回值比较麻烦

async可以控制立即执行或延迟执行,而且接收返回值可以使用future。同时async创建异步任务,自然涉及到线程,它大概率是使用线程池,多次调用可能会复用线程

除了async,还可以通过包装线程入口函数来获取返回值

使用std::packaged_task包装线程入口函数,包装后可以用future获取返回值,配合thread ,future使用

class Work
{
    public:
        int operator() (int a)
        {
            return a+10;        
        }
        int func(int a)
        {
            return a+20;        
        }
}
int main()
{
    Work w;
    //传入一个返回值int,参数int的可调用对象
    packaged_task<int(int)> pt(w);

   //将类成员函数绑定成function传给packaged_task
    packaged_task<int(int)> pt(bind(&Work::func,&w));  
    
    thread mythread(ref(pt),2);
    mythread.join();
    future<int> result = pt.get_future();
    cout << result.get() << endl;
    
    //包装后的packaged_task也可以直接当成函数调用
    //pt(2);
}

future的其他成员函数

int func (int a)
{
    cout << a << endl;
    cout << "thread_id=" << this_thread::get_id() << endl;
}
int main()
{
    future<int> result = async(func,3);
    std::shared_future s_result = result.share();  //转换成shared_future
    result.valid();   //future是否有效(是否绑定某个东西)
    result.wait_for(chrono::milliseconds(100));  //阻塞等待一段时间后返回future状态 
    
    //future状态有三种 future_status::ready 是可以获取值了    
    //future_status::time_out 超时,规定时间内没有变ready
    //future_status::deferred 线程还没有开始执行(创建时用了deferred方式)
}

由于future使用get获取值是移动语义实现,我们需要在多处使用get的时候,应该选择std::shared_future

它的get是复制一份,可以多次调用

//构造async返回future,使用future直接构造shared_future
shared_future<int> result = async(launch::deferred,test); 
shared_future<int> result(async(launch::deferred,test)); 

//根据已有的future构建shared_future,原有的future被move
shared_future<int> res(result.share());
shared_future<int> res(move(result));
shared_future<int> res = move(result);

cout << res.get() << endl;
cout << res.get() << endl;

线程间通信

std::promise提供了线程间通信的手段,类似于传引用,但是其配合future确保数据被写之后才能被另一个线程获取

一般在异步调用时使用,主线程将复杂计算任务交给子线程,然后自己继续执行,后续需要获取计算任务结果时再通过和promise绑定的future获取,能确保获取到计算后的结果,而不是还没计算完成 就被获取。

void mytask(promise<int>& p, int a)
{
    a *= 6;
    a++;
    p.set_value(a);
}

void use_val(future<int>& f)
{
    cout << f.get() << endl;
}

int main()
{
    promise<int> p;   //创建promise对象
    future<int> result = p.get_future();  //future和它绑定
    thread mythread(mytask, ref(p),10);   //执行复杂计算任务,通过传promise引用保存到promise
    mythread.detach();
      //不等待计算线程执行完毕,直接往下执行
    //后续代码...
    thread mythread2(use_val, ref(result));  //另一个任务需要计算结果,传future的引用
    mythread2.join();
    return 0;
}

相关推荐

  1. C# 线(1)

    2024-06-11 04:20:01       27 阅读
  2. C++ 多线

    2024-06-11 04:20:01       23 阅读
  3. C-线

    2024-06-11 04:20:01       16 阅读
  4. C++ 多线

    2024-06-11 04:20:01       12 阅读
  5. C#多线

    2024-06-11 04:20:01       9 阅读

最近更新

  1. ray框架训练阶段和 Serve 阶段对比

    2024-06-11 04:20:01       0 阅读
  2. 大数据开发语言Scala(一) - Scala入门

    2024-06-11 04:20:01       0 阅读

热门阅读

  1. HOT100与剑指Offer

    2024-06-11 04:20:01       5 阅读
  2. 游戏心理学Day10

    2024-06-11 04:20:01       5 阅读
  3. 使用EFCore和Linq查询语句封装复杂的查询结果

    2024-06-11 04:20:01       3 阅读
  4. Python爬虫实现“自动重试”机制的方法(1)

    2024-06-11 04:20:01       3 阅读
  5. OpenAI 发布的 GPT-4o是什么,有什么功能?

    2024-06-11 04:20:01       3 阅读
  6. 算法训练营day52

    2024-06-11 04:20:01       3 阅读