C++多线程编程——thread线程创建与使用(2W字保姆级介绍)

news2025/1/24 8:51:30

目录

前言

线程创建

标准库thread(同步线程的创建过程)

启动线程:实例thread

线程执行单元(可调用对象)

线程等待

线程传参

线程id

成员方法获取线程id

命名空间获取线程id

让出线程资源

sleep_for()

sleep_until

thread的构造函数

thread的成员函数

异步async(异步线程)、future

从实例中理解异步同步线程

std::future可以从异步线程中获取返回值结果

shared_future可以获取多次结果

async参数

std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;(如果没有调用,即使主线程结束,线程入口函数也永远不会被调用)

wait()函数

std::launch::async:在调用async函数的时候就开始创建线程

wait_for函数

同步和异步总结补充

std::promise

单向通信

 双向通信

std::package_task

创建使用(对参数的具体讨论)

线程传参

lambda表达式作为线程执行单元

函数对象作为线程执行单元

成员函数作为线程执行单元

引用传参

类成员作为执行单元的共享成员变量


前言

C++标准库封装了POSIX线程库,将其封装成了一个线程类。学习C++标准库多线程的好处是:第一可以跨平台,第二上层封装可以解决很多不必要的麻烦,同时解决了运行效率问题,尤其对于线程同步来说,比用原生态的C接口简单得多,提供了高效写多线程的一种方式

多进程编程主要考虑的是进程间通信,而多线程主要考虑的是线程同步

线程创建

C++提供了两种线程:同步线程和异步线程

  • 同步执行:同步,是所有的操作都做完,才返回给用户结果
  • 异步执行:异步,不用等所有操作都做完,就响应用户请求

基本上所有实际应用的线程都是异步的,比如点击浏览器进行下载,浏览器正在下载的同时,我们还可以做其它事情。

对于线程的创建,必须要掌握:

第一步:要学会用thread创建同步线程,

第二步:当某些情况下需要使用async创建异步任务,

第三步:在thread和async里最重要的就是future,用future来获取线程返回值结果,当thread创建时需要用packaged_task做绑定包装

第四步:当要做线程间通信时,需要用promise和future做绑定

标准库thread(同步线程的创建过程)

启动线程:实例thread

线程执行单元(可调用对象)

  • 普通函数
  • 函数对象
  • 类成员函数:第二个参数为对象的引用或指针
  • lambda表达式

线程等待

  • void std::thread::join();
  • void std::thread::detach();

线程传参

  • 用detach()时,如果主线程先结束,变量就会被回收;所以用detach()的话,不推荐用引用
  • 如果参数有引用就会发生错误,需要使用std::ref来告诉其传递的是引用。(std::ref()函数的作用:可以实现真正的引用传递)
  • 智能指针做参数传递,可以直接传

线程id

  • std::this_thread命名空间:命名空间保存了当前线程的所有信息
  • std::this_thread::get_id()来获取

成员方法获取线程id

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            sleep(1);
        }
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    cout <<"t1:id = "<<t1.get_id()<<endl;
    cout <<"t2:id = "<<t2.get_id()<<endl;
    t1.join();
    t2.join();
    return 0;
}

注:结果中count=1执行两次是因为两个线程执行次序问题,可以使用条件变量来解决

命名空间获取线程id

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            sleep(1);
        }
        cout <<"t1:id = "<<this_thread::get_id()<<endl;
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        cout <<"t2:id = "<<this_thread::get_id()<<endl;
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    t1.join();
    t2.join();
    return 0;
}

让出线程资源

 在线程中使用this_thread::yield()函数可以交出执行当前线程的CPU去调度其它线程。

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            sleep(1);
            this_thread::yield();
        }
        cout <<"t1:id = "<<this_thread::get_id()<<endl;
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        cout <<"t2:id = "<<this_thread::get_id()<<endl;
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    t1.join();
    t2.join();
    return 0;
}

sleep_for()

作用和sleep一样,但可以实现精确传参。作用是使线程休眠某个指定的时间片(time span),该线程才被重新唤醒。

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            //sleep(1);
            this_thread::sleep_for(3000ms);
        }
        cout <<"t1:id = "<<this_thread::get_id()<<endl;
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        cout <<"t2:id = "<<this_thread::get_id()<<endl;
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    t1.join();
    t2.join();
    return 0;
}

sleep_until

线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

作用:阻塞当前正在执行的线程直到sleep_time溢出。

sleep_time是和时钟相关联的,也就是要注意时钟调整会影响到sleep_time。

因此, 时钟的时长有可能或没有可能会短于或长于sleep_time。Clock::now()返回调用这个函数时的时间,取决于调整方向。该函数也有可能因为调度或者资源竞争而导致阻塞时间延长到sleep_time溢出之后。

#include <iostream>  
#include <iomanip>  
#include <chrono>  
#include <ctime>  
#include <thread>  
#pragma warning(disable:4996)//加上可去掉unsafe 请使用localtime_s的编译报错  
int main()  
{  
    using std::chrono::system_clock;  
    std::time_t tt = system_clock::to_time_t(system_clock::now());  
    struct std::tm *ptm = std::localtime(&tt);  
    std::cout << "Current time: " << std::put_time(ptm, "%X") << '\n'; //必须大写X,若小写x,输出的为日期  
    std::cout << "Waiting for the next minute to begin...\n";  
    ++ptm->tm_min;   
    ptm->tm_sec = 0;  
    std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));  
    std::cout << std::put_time(ptm, "%X") << "reached!\n";  
    getchar();  
    return 0;  
}  

 

thread的构造函数

在C++中,std::thread的构造函数可以接受一个可调用的对象(函数、函数指针、lambda表达式、bind表达式等),以此来指定在新线程中要执行的任务。

以下是std::thread的几种常见的构造函数形式:

1.默认构造函数:创建一个表示未开始执行的线程的对象。

std::thread();

2.接受一个可调用的对象作为参数,并开始一个新的线程来执行该任务。

template <typename Function, typename... Args>
explicit thread(Function&& f, Args&&... args);

例如:

void my_function(int x, int y) { ... }
int main() {
    std::thread t(&my_function, 10, 20);  // 创建一个新线程来执行 my_function,参数为 10 和 20
    t.join();  // 等待新线程执行完毕
    return 0;
}

3.接受两个可调用的对象作为参数,并开始一个新的线程来执行这两个任务。这是两个任务的并行执行,不是第一个任务等待第二个任务。

template <typename Function1, typename Function2>
void thread(Function1&& f1, Function2&& f2);

例如:

void my_function1() { ... }
void my_function2() { ... }
int main() {
    std::thread t1(&my_function1);  // 创建一个新线程来执行 my_function1
    std::thread t2(&my_function2);  // 创建一个新线程来执行 my_function2
    t1.join();  // 等待第一个线程执行完毕
    t2.join();  // 等待第二个线程执行完毕
    return 0;
}

thread的成员函数

常用函数在前面和后面都有具体介绍,重要的有join、detach、get_id和native_handle。

此外C++11的线程可以使用平台的特性拓展

std::native_handle_type std::native_handle();

该函数的作用是获取当前平台真正的线程id,如windows就返回windows的线程id,Linux就返回Linux的线程id。转换之后就可以通过线程id使用C语言的线程接口了。

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            //sleep(1);
            this_thread::sleep_for(3000ms);
        }
        cout <<"t1:id = "<<this_thread::get_id()<<endl;
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        cout <<"t2:id = "<<this_thread::get_id()<<endl;
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    pthread_t id1 = t1.native_handle();
    pthread_t id2 = t2.native_handle();

    pthread_join(id1,nullptr);
    pthread_join(id2,nullptr);
    // t1.join();
    // t2.join();
    return 0;
}

异步async(异步线程)、future

std::async用于创建异步任务,实际上就是创建一个线程执行相应任务

std::futureresult = std::async(mythread);//创建一个线程并开始执行,绑定关系,流程并不卡在这里

std::futureresult = std::async(std::launch::deferred,&A::mythread2,&a,tmppar);//第二个参数是对象引用,才能确保线程里面是同一个对象

从实例中理解异步同步线程

创建同步线程,当主线程退出时,不用detach和join会导致子线程未退出而造成资源泄露。

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    thread t1(mythread,5);//创建同步线程

    //async(mythread,5);//创建异步线程
    return 0;
}

当使用异步线程,则可以正常打印(创建异步线程,会使主线程阻塞,在次线程执行完之后再继续执行)。异步任务不属于当前开销,即使主线程结束,次线程任务仍可执行

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    async(mythread,5);//创建异步线程
    for(int i=0;i<3;i++)
    {
        cout <<"main exit!"<<endl;
        this_thread::sleep_for(1s);
    }
    return 0;
}

std::future可以从异步线程中获取返回值结果

future可以和async结合使用,来接收异步线程的返回值,当结合时,主线程将不会阻塞,可以实现真正的异步任务操作

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    future<int>result = async(mythread,5);//创建异步线程
    for(int i=0;i<3;i++)
    {
        cout <<"main exit!"<<endl;
        this_thread::sleep_for(1s);
    }
    return 0;
}

在主线程使用future::get方法会使主线程阻塞等待次线程结束

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    future<int>result = async(mythread,5);//创建异步线程
    for(int i=0;i<3;i++)
    {
        cout <<"main exit!"<<endl;
        this_thread::sleep_for(1s);
    }
    cout<<result.get()<<endl;
    return 0;
}

shared_future可以获取多次结果

使用std::future创建的对象只可以调用一次get方法来获取线程结果,但使用shared_future可以共享某个共享状态的最终结果,可以拷贝多个

#include <iostream>
#include <thread>
#include <unistd.h>
#include <future>
using namespace std;

int thread_func(int num)
{
    for(int i=0;i<3;i++)
    {
        cout <<"num = "<<num<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    packaged_task<int(int)>mt(thread_func);
    thread t(ref(mt),10);
    t.join();
    cout <<"thread exit!"<<endl;
    shared_future<int>result = mt.get_future();

    cout <<"result1 = "<<result.get()<<endl;
    cout <<"result2 = "<<result.get()<<endl;
    return 0;
}

async参数

std::launch::deferred:表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;(如果没有调用,即使主线程结束,线程入口函数也永远不会被调用)

 

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    //future<int>result = async(mythread,5);//创建异步线程
    future<int>result1 = async(std::launch::deferred,mythread,6);

    //cout<<result1.get()<<endl;
    return 0;
}

wait()函数

使用wait()方法只用于通知此线程任务开始执行

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    //future<int>result = async(mythread,5);//创建异步线程
    future<int>result1 = async(std::launch::deferred,mythread,6);
    cout << "main exit!"<<endl;
    result1.wait();
    return 0;
}

std::launch::async:在调用async函数的时候就开始创建线程

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程

    //future<int>result = async(mythread,5);//创建异步线程
    future<int>result1 = async(std::launch::async,mythread,6);

    //cout<<result1.get()<<endl;
    return 0;
}

wait_for函数

wait_for函数第一个作用是用于创建同步线程时,主线程固定阻塞一段时间后退出

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程
    packaged_task<int(int)>mt(mythread);
    future<int>result=mt.get_future();
    thread t(ref(mt),5);
    //future<int>result = async(mythread,5);//创建异步线程
    //future<int>result1 = async(std::launch::async,mythread,6);
    cout << "main exit!"<<endl;
    result.wait_for(500ms);
    return 0;
}

wait_for第二个作用是用来判断当前某个线程状态

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int mythread(int num)
{
    for(int i=0;i<3;i++)
    {
        cout << "hello world"<<endl;
        this_thread::sleep_for(1s);
    }
    return num+5;
}

int main(int argc, char const *argv[])
{
    //thread t1(mythread,5);//创建同步线程
    //packaged_task<int(int)>mt(mythread);
    //future<int>result=mt.get_future();
    //thread t(ref(mt),5);
    //future<int>result = async(mythread,5);//创建异步线程
    future<int>result1 = async(std::launch::deferred,mythread,6);
    cout << "main exit!"<<endl;
    //result.wait_for(500ms);
    future_status status;
    do{
        this_thread::sleep_for(500ms);
        status = result1.wait_for(std::chrono::seconds(1));
        if(status == std::future_status::deferred){
            std::cout<<"deferred\n";
        }else if(status == std::future_status::timeout){
            std::cout<<"timeout\n";
        }else if(status == std::future_status::ready){
            std::cout<<"ready!\n";
        }
    }while(status !=std::future_status::ready);

    return 0;
}

同步和异步总结补充

  • 同步执行:同步,是所有的操作都做完,才返回给用户结果
  • 异步执行:异步,不用等所有操作都做完,就响应用户请求
  • 处理耗时操作(数据库大量写入或者查询、文件下载、复杂计算)
    • 同步操作:所有的操作都做完,才返回给用户,这样用户在线等待的时间太长,给用户一种卡死了的感觉
    • 异步操作:即先响应用户请求然后慢慢去执行耗时操作,用户体验较好

std::promise

对于线程间的通信,我们一般通过全局变量来实现,但在C++中通过std::promise也可以实现线程间的通信,相对于全局变量,不需要用锁来确保安全性

  • 类模板,std::promise保存的值可被与之关联的std::promise读取,读取操作可以发生再其它线程
  • 作用:std::promise和std::future合作共同实现了多线程间通信
  • 注意事项
    • std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值)
    • set_value只能被调用一次,多次调用会抛出std::future_error异常
    • 一个std::promise实例只能与一个std::future关联共享状态,当在同一个std::promise上反复调用get_future会抛出future_error异常
    • 通过std::promise让std::future抛出异常
      • std::promise虽然支持自定义异常,但它并不直接接受异常对象
      • 自定义异常可以通过位于头文件exception下的std::make_exception_ptr函数转化为std::exception_ptr。它表示异常的指针。这个指针可以用于在不同的上下文中传递和处理异常,例如在多线程环境中传递异常,或者在捕获异常后延迟处理。
    • std::promise此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞

单向通信

#include <iostream>
#include <thread>
#include <future>
using namespace std;
//程序目的:想要把线程1中的iVal值传给线程2
void Thread_Fun1(std::promise<int> &p)
{
    //为了突出效果,可以使线程休眠5s
    this_thread::sleep_for(std::chrono::seconds(5));

    int iVal = 233;
    cout<<"传入数据(int)"<<iVal<<endl;

    //传入数据iVal
    p.set_value(iVal);
}
//发送者必须定义有promise,接收者必须定义有future
void Thread_Fun2(std::future<int> &f)
{
    //阻塞函数,直到收到相关联的std::promise对象传入的数据
    auto iVal = f.get();//iVal=233

    cout<<"收到数据(int)"<<iVal<<endl;
}
int main(int argc, char const *argv[])
{
    //声明一个std::promise对象prl,其保存的值类型为int
    promise<int> prl;
    //声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
    future<int>ful = prl.get_future();

    //创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
    std::thread t1(Thread_Fun1,std::ref(prl));
    //创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
    std::thread t2(Thread_Fun2,std::ref(ful));

    //阻塞至线程结束
    t1.join();
    t2.join();
    return 0;
}

 双向通信

#include <iostream>
#include <thread>
#include <future>
using namespace std;
//程序目的:想要把线程1中的iVal值传给线程2
void Thread_Fun1(std::promise<int> &p,std::future<int> &f)
{
    //为了突出效果,可以使线程休眠5s
    this_thread::sleep_for(std::chrono::seconds(5));

    int iVal = 233;
    cout<<"传入数据(int)"<<iVal<<endl;

    //传入数据iVal
    p.set_value(iVal);

    auto num = f.get();

    cout<<"num(int)"<<iVal<<endl;
}
//发送者必须定义有promise,接收者必须定义有future
void Thread_Fun2(std::future<int> &f,std::promise<int> &p)
{
    //阻塞函数,直到收到相关联的std::promise对象传入的数据
    auto iVal = f.get();//iVal=233

    cout<<"收到数据(int)"<<iVal<<endl;

    iVal = 250;
    
    p.set_value(iVal);
}
int main(int argc, char const *argv[])
{
    //声明一个std::promise对象prl,其保存的值类型为int
    promise<int> prl1;
    promise<int> prl2;

    //声明一个std::future对象ful,并通过std::promise的get_future()函数与prl绑定
    future<int>ful1 = prl1.get_future();
    future<int>ful2 = prl2.get_future();

    //创建一个线程t1,将函数Thread_Fun1及对象prl放在线程里面执行
    std::thread t1(Thread_Fun1,std::ref(prl1),std::ref(ful2));
    //创建一个线程t2,将函数Thread_Fun2及对象ful放在线程里面执行
    std::thread t2(Thread_Fun2,std::ref(ful1),std::ref(prl2));

    //阻塞至线程结束
    t1.join();
    t2.join();
    return 0;
}

std::package_task

std::package_task它允许传入一个函数,并将函数计算的结果传给std::future,包括函数运行时产生的异常

注意事项(拓展)

  • std::package_task支持move,但不支持拷贝(copy)
  • get_future仅能调用一次,多次调用会触发std::future_error异常
  • std::packaged_task::valid该函数用于判断std::packaged_task对象是否是有效状态
  • std::packaged_task::reset()使std::packged_task可以执行多次

使用实例

package_task相对于一个包装器,如function:可以将要调用的对象包装成统一类型。比如我们想要获取线程退出的结果,再C接口中的int pthread_join(pthread_t thread,void **retval);中的retval就用于存放线程id退出的返回值。但使用C++的join无法获得线程退出的返回值。

我们可以使用package_task配合future进行返回值的绑定,头文件为<future>

#include <iostream>
#include <thread>
#include <unistd.h>
#include <future>
using namespace std;

int thread_fun1(int num)
{
    cout << "num = "<<num<<endl;
    int count = num + 5;
    return count;//返回值
}
int main(int argc, char const *argv[])
{
    //使用包装器包装线程单元,包装类型为<返回值类型(形参类型)>
    packaged_task<int(int)>mt(thread_fun1);//packaged_task为类模板
    future<int>result = mt.get_future(); //future也为类模板,要指定线程的返回值类型
    /*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
    //包装后需要按引用来传参
    thread t(std::ref(mt),5);
    
    t.join();

    cout<<"result count = "<<result.get()<<endl;

    return 0;
}

其中future的get方法会阻塞,直到线程结束为止,获取返回值

#include <iostream>
#include <thread>
#include <unistd.h>
#include <future>
using namespace std;

int thread_fun1(int num)
{
    cout << "num = "<<num<<endl;
    int count = num + 5;
    this_thread::sleep_for(3s);
    return count;//返回值
}
int main(int argc, char const *argv[])
{
    //使用包装器包装线程单元,包装类型为<返回值类型(形参类型)>
    packaged_task<int(int)>mt(thread_fun1);//packaged_task为类模板
    future<int>result = mt.get_future(); //future也为类模板,要指定线程的返回值类型
    /*result和mt的返回值做了绑定,一旦thread_fun1执行完了,就会将返回值传给result*/
    //包装后需要按引用来传参
    thread t(std::ref(mt),5);
    
    cout<<"result count = "<<result.get()<<endl;//其中future的get方法会阻塞,直到线程结束为止,获取返回值
    cout <<"thread exiting!!!"<<endl;
    t.join();
    return 0;
}

创建使用(对参数的具体讨论)

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

void mythread(void)//线程函数可以随便写,参数和返回值不固定
{
    for(int i=0;i<3;i++)
    {
        cout<<"hello world"<<endl;
        sleep(3);
    }
}

int main(int argc, char const *argv[])
{
    thread t(mythread);//使用构造函数,传参为线程执行单元

    t.join();//pthread_join

    return 0;
}

线程传参

对于线程传参,直接在对象后面添加传入参数

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

void mythread(int num)//线程函数可以随便写,参数和返回值不固定
{
    for(int i=0;i<3;i++)
    {
        cout << "num = "<<num<<endl;
        cout<<"hello world"<<endl;
        sleep(3);
    }
}

int main(int argc, char const *argv[])
{
    //thread t(mythread);//使用构造函数,传参为线程执行单元
    thread t(mythread,5);

    t.join();//pthread_join

    return 0;
}

如果想要传入字符串,会报错,这是因为传入字符串,构造函数会默认转化成string类的对象

使用string,就不会报错

或者使用传入字符串指针或者数组

lambda表达式作为线程执行单元

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
int main(int argc, char const *argv[])
{
    auto F = [](int num,const char *str)
    {
        for(int i=0;i<3;i++)
        {
            cout << "num = "<<num<<endl;
            cout <<str<<endl;
            cout<<"lambda func"<<endl;
            sleep(1);
        }
    };
    const char *ptr = "hello";
    //thread t(mythread);//使用构造函数,传参为线程执行单元
    thread t(F,5,ptr);

    t.join();//pthread_join

    return 0;
}

函数对象作为线程执行单元

... ...
class Test
{
    public:
        void operator()(int num,const char *ptr)
        {
            for(int i=0;i<3;i++)
            {
                cout << "num = "<<num<<endl;
                cout <<ptr<<endl;
                cout<<"Function object"<<endl;
                sleep(1);
            }
        }
};

int main(int argc, char const *argv[])
{
    const char *ptr = "hello";
    Test test;
    thread t(test,5,ptr);
    t.join();//pthread_join

    return 0;
}

成员函数作为线程执行单元

线程构造函数第一个参数为成员函数时,由于成员函数的地址代表在类中的偏移量,因此第二个参数必须是对象的本身,可以是对象、对象的地址或者对象的引用

 

class MyThread
{
public:
    void myfun(int num, const char *ptr)
    {
        for(int i=0;i<3;i++)
        {
            cout << "num = "<<num<<endl;
            cout <<ptr<<endl;
            cout<<"Function object"<<endl;
            sleep(1);
        }       
    }
};
int main(int argc, char const *argv[])
{
    MyThread mt;
    const char *ptr = "hello";
    thread t(&MyThread::myfun,mt,5,ptr);
    t.join();//pthread_join

    return 0;
}

引用传参

如果参数传递引用

比如传入一个对象的左值引用,则会报错

在传入引用的时候,我们必须使用`std::ref `,它是 C++ 标准库中的一个函数模板,它的作用是将传递给它的对象包装成一个可以传递给函数的引用包装器。这样可以让那些原本不接受引用的函数接受引用参数,从而实现在函数中修改传入对象的值。

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

void mythread(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
{
    for(int i=0;i<3;i++)
    {
        cout << "num = "<<num<<endl;
        cout <<str<<endl;
        cout<<"hello world"<<endl;
        sleep(3);
    }
}

int main(int argc, char const *argv[])
{

    int num = 5;
    int &l_num = num;//基本类型左值引用

    string s = "8.23";
    string &ls = s;
    thread t(mythread,std::ref(l_num),std::ref(ls));
    t.join();//pthread_join

    return 0;
}

C++线程不能直接传引用的原因是为了防止次线程还没来得及退出,主线程就结束了。比如我们忘记使用join(由主线程回收资源)或者使用了detach(主线程退出后,由系统回收线程资源),string &rs创建的rs对象当主线程退出时就会释放,rs作为次线程传入的参数,而次线程还没来得及退出,这就会导致安全性的问题。因此编译器默认引用传递存在危险,因此使用ref。

例:使用引用传递在线程中修改变量值

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void mythread2(int &num,string& str)//线程函数可以随便写,参数和返回值不固定
{

        cout << "num = "<<num<<endl;
        cout <<str<<endl;
        str = "2023";
}

int main(int argc, char const *argv[])
{

    int num = 5;
    int &l_num = num;//基本类型左值引用

    string s = "8.23";
    string &ls = s;
    thread t(mythread2,std::ref(l_num),std::ref(ls));
    t.join();//pthread_join
    cout << s << endl;

    //t.detach();
    return 0;
}

如果接收参数是右值引用,不能用ref

需要使用move强制转换为右值引用再传参

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;
void mythread3(int &&num,string& str)//线程函数可以随便写,参数和返回值不固定
{

        cout << "num = "<<num<<endl;
        cout <<str<<endl;
        str = "2023";
}

int main(int argc, char const *argv[])
{

    int num = 5;
    int &l_num = num;//基本类型左值引用

    string s = "8.23";
    string &ls = s;
    thread t(mythread3,std::move(l_num),std::ref(ls));
    t.join();//pthread_join
    cout << s << endl;

    //t.detach();
    return 0;
}

类成员作为执行单元的共享成员变量

当使用类成员函数作为执行单元,传递对象,无法共用成员变量,传递对象的引用或者地址可以共用成员变量。

如传递对象,则结果如下:

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            sleep(1);
        }
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,mt);
    thread t2(&MyThread::myfun2,mt);

    t1.join();
    t2.join();
    return 0;
}

可以发现打印结果全为0

如果使用地址或者引用,结果如下:

#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;

class MyThread
{
public:
    int myfun1()
    {
        for(int i=0;i<3;i++)
        {
            m_count++;
            sleep(1);
        }
        return 0; 
    }

    int myfun2()
    {
        for(int i=0;i<3;i++)
        {
            cout << "count = "<<m_count << endl;
            sleep(1);
        }   
        return 0;    
    }

    int m_count=0;
};

int main(int argc, char const *argv[])
{
    MyThread mt;
    thread t1(&MyThread::myfun1,&mt);
    thread t2(&MyThread::myfun2,&mt);

    t1.join();
    t2.join();
    return 0;
}

发现线程操作成员变量正常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/924529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊一聊微前端框架的选型和实现 | 业务平台

一、项目背景 目前&#xff0c;我们开发维护的项目主要有 6 个&#xff0c;但是分别对应 PC 和 H5 两个端&#xff1a; 如上图所示&#xff0c;我们 6个项目最开始是一个一个进行开发维护的&#xff0c;但是到后期&#xff0c;这几个项目之间有的部分会有业务逻辑不同&#xff…

docker高级(mysql主从复制)

数据库密码需要设置成自己的&#xff01;&#xff01;&#xff01; 1、创建容器master13307 #docker pulldocker run -p 13307:3306 --name mysql-master \ --privilegedtrue \ -v /mysql/mysql-master/log:/var/log/mysql \ -v /mysql/mysql-master/data:/var/lib/mysql \ -…

centos 下扩容根目录

大体情况&#xff1a; 在VM虚拟机上安装了移动云的BCLinux镜像&#xff0c;磁盘设定为8G&#xff0c;但是用过一段时间之后根目录下磁盘已满&#xff0c;无法创建文件夹等操作&#xff0c;因此在VM上进行了磁盘扩容&#xff0c;扩容之后需要在系统上自行挂载&#xff0c;使用m…

【VsCode】SSH远程连接Linux服务器开发,搭配cpolar内网穿透实现公网访问(1)

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

【linux】基本指令(二)【man、echo、cat、cp】

目录 一、man指令二、echo指令三、cat指令二、cp指令一些常见快捷键 一、man指令 Linux的命令有很多参数&#xff0c;我们不可能全记住&#xff0c;可以通过查看联机手册获取帮助。访问Linux手册页的命令是 man 语法: man [选项] 命令 常用选项 1.-k 根据关键字搜索联机帮助 2…

面试题-React(六):React组件和生命周期

一、React组件 React组件简介&#xff1a; React组件是构建用户界面的基本单元。它们将界面拆分成独立、可重用的部分&#xff0c;使得代码更加模块化、可维护性更高。React组件可以是函数组件或类组件&#xff0c;它们接收输入的数据&#xff08;称为props&#xff09;并返回…

ORB-SLAM2算法11之地图点MapPoint

文章目录 0 引言1 MapPoint类1.1 构造函数1.2 成员函数1.2.1 AddObservation1.2.2 EraseObservation1.2.3 SetBadFlag1.2.4 Replace1.2.5 ComputeDistinctiveDescriptors1.2.6 UpdateNormalAndDepth1.2.7 PredictScale 2 MapPoint类用途 0 引言 ORB-SLAM2算法7详细了解了Syste…

Webstorm 入门级玩转uni-app 项目-微信小程序+移动端项目方案

1. Webstorm uni-app语法插件 &#xff1a; Uniapp Support Uniapp Support - IntelliJ IDEs Plugin | Marketplace 第一个是不收费&#xff0c;第二个收费 我选择了第二个Uniapp Support &#xff0c;有试用30天&#xff0c;安装重启webstorm之后&#xff0c;可以提高生产率…

排序链表-归并排序

给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5] 示例 3&#xff1a; 输…

vue 展开和收起

效果图 代码块 <div><span v-for"(item,index) in showHandleList" :key"item.index"><span>{{item.emailFrom}}</span></span><span v-if"this.list.length > 4" click"showAll !showAll">{…

Ceph入门到精通-大流量10GB/s LVS+OSPF 高性能架构

LVS 和 LVSkeepalived 这两种架构在平时听得多了&#xff0c;最近才接触到另外一个架构LVSOSPF。这个架构实际上是LVSKeepalived 的升级版本&#xff0c;我们所知道LVSKeepalived 架构是这样子的&#xff1a; 随着业务的扩展&#xff0c;我们可以对web服务器做水平扩展&#xf…

聚观早报 | 云鲸扫拖机器人J4体验;芯科科技第三代无线开发平台

【聚观365】8月24日消息 云鲸扫拖机器人J4体验 芯科科技推出第三代无线开发平台 英伟达与VMWare宣布扩大合作 万物新生&#xff08;爱回收&#xff09;2023年二季度财报 充电桩需求增长带动汽车后服务市场 云鲸扫拖机器人J4体验 家庭卫生清洁是每个人都无法回避的事情&am…

实训笔记8.24

实训笔记8.24 8.24笔记一、Sqoop数据迁移工具1.1 Sqoop的基本概念1.2 Sqoop的基本操作1.2.1 命令语法1.2.2 list-databases1.2.3 list-tables1.2.3 eval1.2.4 import1.2.5 export1.2.6 导入 二、Flume日志采集工具2.1 数据采集的问题2.2 数据采集一般使用的技术2.3 扩展&#x…

Tokenview再度升级:全新Web3开发者APIs数据服务体验!

Tokenview发布全新版本的区块链APIs和数据服务平台&#xff0c;为开发者打造更强大、更便捷的开发体验&#xff01; 此次升级&#xff0c;我们整合了开发者使用习惯以及Tokenview产品优势。我们深知对于开发者来说&#xff0c;时间是非常宝贵的&#xff0c;因此我们努力提供一…

联合注入步骤

使用场景&#xff1a; 有回显&#xff0c;可以看到某些字段的回显信息 像下面的有具体的回显信息 一、判断注入位点 在原始的id&#xff08;参数&#xff09;的输入后面添加额外的条件 如果and 11 有结果&#xff0c;and10没有结果输出&#xff0c; 就说明我们添加的额外条件…

sqlmap安装以及运用

目录 一、sqlmap简介 linux系统安装 windows系统安装 二.sqlmap确定目标 (1) sqlmap直连数据库 (2) sqlmap的URL探测 (3) Sqlmap文件读取目标 (4) Sqlmap Google批量扫注入 一、sqlmap简介 sqlmap是一个开源的渗透测试工具&#xff0c;它可以自动化检测sql注入漏洞利用…

opencv 文档识别+UI界面识别系统

目录 一、实现和完整UI视频效果展示 主界面&#xff1a; 识别结果界面&#xff1a; 查看处理图片过程&#xff1a; 查看历史记录界面&#xff1a; 二、原理介绍&#xff1a; 将图像变换大小->灰度化->高斯滤波->边缘检测 轮廓提取 筛选第三步中的轮廓&#xf…

Unity OnDrawGizmos的简单应用 绘制圆形

编辑器和配置表各有各的好。 卡牌游戏即使再复杂&#xff0c;哪怕是梦幻西游&#xff0c;大话西游那种&#xff0c;甚至wow那种&#xff0c;用配表都完全没问题。但是崩坏3&#xff0c;或者鬼泣&#xff0c;格斗游戏&#xff0c;可视化编辑器是唯一的选择。 开发初期刚开始配技…

2.redis数据结构之Hash

Hash-散列类型:H 为什么选择Hash? 假设有User对象以JSON序列化的形式存储到Redis中&#xff0c;User对象有id&#xff0c;username、password、age、name等属性&#xff0c;存储的过程如下&#xff1a; 保存、更新&#xff1a; User对象 -> json(string) -> redis 如果在…

基于mha+mycat2+gtid的半同步主从复制双vip高可用MySQL集群

目录 项目名称 项目架构图 项目概述 项目准备 项目步骤 一、使用ansible编写palybook实现4台二进制安装MySQL环境的部署&#xff0c;并把master上的基础数据下发到所有slave服务器上 1. 建立免密通道 2.安装ansible在ansible服务器上&#xff0c;并写好主机清单 3.将…