文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C++初级线程使用方法有哪些

2023-06-22 05:24

关注

这篇文章主要讲解了“C++初级线程使用方法有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++初级线程使用方法有哪些”吧!

1 线程启动

C++11中,线程的启动终究是对std::thread的对象进行构造。

线程构造的类别如下:

1.1  线程函数无参数无返回值

此类可以说是最简单的线程启动,函数不需要传参也不需要返回函数执行结果,执行完成后,线程自动退出。

形如:

void FunDoingNothing();std::thread(FunDoingNothing)

编写代码时,需要加上<thread>头文件以方便编译器能够正确处理thread对象。

1.2  线程函数有参数无返回值

C+=11中,thread的构造函数中使用了可变参数,这样,可以使得构造thread对象时可以自定义传入参数,

构造函数的定义如下:

template<class F, class... Args> explicit thread(F&& f, Args&&... args);

在实际使用时,线程函数有参数时可以定义形式如下:

void printMsg(int a, int b) {  cout << "input params are:" << a <<","<<b<< endl;}std::thread my_thread(printMsg, 3, 4)

1.3  调用可调用的类型构造

使用时,可以将带有执行函数的变量传入thread的构造函数中从而替换默认的构造函数,

如下:

using namespace std;class BackGroundTask{ public:    void operator()() const{        doSomeThing();    }priavte:    doSomeThing();};int main(){    BackGroundTask f;    std::thread myThread(f);}

上面的代码中,在启动线程时同构构造对象f,f对象的重载函数中调用了线程运行时要执行的方法。但有一点需要注意的是,在传入临时的构造对象时,不经过处理,可能会让编译器产生错误的理解。

如:

std::thread myThread(BackGroundTask());

这里相当与声明了一个名为myTread的函数, 这个函数带有一个参数(函数指针指向没有参数并返回BackGroundTask对象的函数), 返回一个 std::thread 对象的函数, 而非启动了一个线程。

如果要解决这个问题,只需要如下处理即可:

std::thread myThread((BackGroundTask()));std::thread myThread{BackGroundTask()};

当然,也可以使用lamda表达式实现上述功能,如下:

std::thread myThread([]{  doSomeThing();  });

2 等待线程

C++11中,确保线程执行完后,主线程在退出,需要在代码中使用join()函数,这样就可以保证变量在线程结束时才会进行销毁。

2.1 join等待

在实际编程时,join函数只是简单的等待或者不等待。在有些场景下就会不使用,如果想要进行更加灵活的控制,需要使用C++11中提供的其他机制,这个也会在后面的推文中进行说明。
在编程时,如果对一个线程使用了join,那么在后续的操作中如果使用joinable()执行结果将返回false。既一旦使用了join。线程对象将不能重复使用。如下代码中,在线程中使用join等待。

class BackGroundTask{ public:    void operator()(){        doSomeThing();    } private:    void doSomeThing() {cout<<"线程退出"<<endl;};};int main(){    BackGroundTask f;    std::thread myThread(f);    myThread.join();    cout<<"退出"<<endl;}

上面的代码使用了线程等待,可以输出正确的结果,如下:

线程退出
退出

如果将 myThread.join()语句注释,再次执行时,程序将执行出错,因为在子线程还没有结束时,主线程已经结束。

运行结果如下:

退出
terminate called without an active exception

上面的输出具备不确定性,代码运行时结果随机。

2.2 异常场景的join等待

异常场景中,如果没有充分考虑join的位置,就可能会产生因为异常导致主线程先于子线程退出的情况,解决这些问题可以通过下面两种方法进行处理:

2.2.1  通过异常捕获

通过分析代码中的异常场景,对异常使用try...catch进行捕获,然后在需要线程等待的地方调用join()函数,这种方法虽然可以轻易地捕获问题并对问题进行修复,但并非是通用法则,还需要根据实际情况进行分析。如检查并确认是否线程函数中是否使用了局部变量的引用等其它原因。

2.2.2 使用RAII方式进行线程等待

RAII可以理解为资源获取既初始化。因为全写为:Resource Acquisition Is Initialization
实际使用时,通过定义一个类,然后在析构函数中使用join函数进行线程等待。这样可以避免场景有遗漏的地方。

class thread_guard{private:    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;};

 如上,通过在将线程对象传入到类thread_guard中,如果thread_guard类对象的局部变量被销毁,则在析构函数中会将线程托管到原始线程。
thread_guard中,使用delete标识,禁止生成该类的默认拷贝构造、以及赋值函数。
在实际编程时如果不想线程等待,可以使用detach方法,将线程和主线程进行分离。

3 线程分离

线程分离使用detach方法,使用后将不能在对已分离的线程进行管理,但是分离的线程可以真实的在后台进行运行。当线程退出时,C++会对线程资源进行清理和回收。
线程分离通常被用作守护线程或者后台工作线程。

使用方法如下:

int main(){    BackGroundTask f;    std::thread myThread(f);    myThread.detach();    cout<<"退出"<<endl;}

4 向线程传递参数

向线程传递参数非常简单,在上面的代码中也有提及,这里主要说下向线程中传递参数的陷阱。

看下面的代码:

void f(int i,std::string const& s);void oops(int some_param){char buffer[1024]; sprintf(buffer, "%i",some_param);std::thread t(f,3,buffer);t.detach();}

上面的代码中buffer是一个局部指针变量,使用后,可能会导致线程出现未定义的行为,因为从char*到string的转换时使用的是隐式转换,但是thread在使用时会将变量拷贝到线程私有内存,但是并不知道需要将参数进行转换,因此复制到私有内存的变量就没有转换成期望的对象。
如果要解决这个问题,可以在使用时直接将参数类型转换成函数默认的类型,在上面的例子中可以

做如下操作:

std::thread t(f,3,std::string(buffer));

但是这样做依然存在问题,既线程在复制变量到私有内存时,只复制了变量值,这样在线程调用后,如果继续使用线程函数处理后的变量时可能变量并没有改造,依旧是线程调用之前的变量。
因此要想在函数传参过程中使得线程拷贝时依旧保持引用,可以在线程调用时使用引用方式,

如:

std::thread t(f,3,std::ref(std::string(buffer)));

5 线程识别

每个线程都有一个线程标识,在C++11中,线程标识通过std::thread::id进行标识,std::thread::id可以复用并进行比较,如果两个线程的id相等,那么它们就是同一个线程或者没有线程,如果不等就表示两个是不同的线程或者其中一个线程不存在。

线程id的获取方法有两种,如下:

5.1 thread成员函数获取

通过std::thread::get_id()可以获取线程的id。

使用方法如下:

int main(){    BackGroundTask f;    std::thread myThread(f);    cout<<"线程id:"<<myThread.get_id()<<endl;    myThread.detach();    cout<<"退出"<<endl;}

线程运行结果为:

5.2 std::this_thread::get_id()

线程id可以用来区分主线程和子线程,通过std::this_thread::get_id()可以先将主线程id保存,然后在和子线程进行比较,从而区分主线程和子线程。

代码如下:

int main(){    std::thread::id master_thread=std::this_thread::get_id();    BackGroundTask f;    std::thread myThread(f);    if(master_thread!=myThread.get_id())    {        cout<<"子线程id:"<<myThread.get_id()<<endl;    }    myThread.detach();    cout<<"退出"<<endl;}

代码中,先保存了主线程的id标识,然后获取子线程id,比较两个线程id。如果不相等则输出子线程id。

代码运行结果如下:

子线程id:140161423791872

感谢各位的阅读,以上就是“C++初级线程使用方法有哪些”的内容了,经过本文的学习后,相信大家对C++初级线程使用方法有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯