iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++多线程传参的实现方法
  • 713
分享到

C++多线程传参的实现方法

C++多线程传参C++线程传参 2023-05-17 08:05:13 713人浏览 安东尼
摘要

目录1.线程传参的过程1.1 内置类型的实参1.2 类类型的实参1.3 传入智能指针unique_ptr1.线程传参的过程 下面是thread的源代码 template< cl

1.线程传参的过程

下面是thread的源代码

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

源代码很复杂,反正我是看不懂。但是有一点可以确定,默认情况下实参都是按值传入产生一个副本到thread中(很多人可能都见过这句话,但可能不清楚具体细节,下面举例说明)

实参从主线程传递到子线程的线程函数中,需要经过两次传递。第1次发生在std::thread构造时,实参按值传递并以副本形式被保存到thread的tuple中,这一过程发生在主线程。第2次发生在向线程函数传递时,此次传递是由子线程发起即发生在子线程中,并将之前std::thread内部保存的副本以右值的形式(通过调用std::move())传入线程函数。

1.1 内置类型的实参

1.1.1参数按值传递

默认情况下,所有参数(含第1个参数可调用对象)均按值并以副本的形式保存在std::thread对象中的tuple里。

这一点的实现类似于std::bind(不了解bind的可以去学习一下)

void func(int& a) //左值引用
{
    a = 6;
}
 
int main()
{
    int b = 1;
    thread t1(func,b); //错误。对实参b按值拷贝产生一个副本,将该副本存放在thread的tuple,
                       //随后对副本 调用std::move,产生一个右值,而func中的参数a是左值
                       //引用,不能绑定到右值
    cout << b << endl;
    t1.join();
    return 0;
}

1.1.2如果想按引用传递,则需要调用std::ref

void func(int& a) //左值引用
{
    a = 6;
}
 
int main()
{
    int b = 1;
    thread t1(func,std::ref(b); //std::ref传参时,先会创建一个std::ref类型的临时对象,
                           //其中保存着对b的引用。然后这个std::ref再以副本的形式保存在
                         //thread的tuple中。随后这个副本被move到线程函数,由于std::ref重载了
                        //operator T&(),因此会隐式转换为int&类型,因此起到的效果就好象b直接
                          //被按引用传递到线程函数中来
 
    cout << b << endl;//b的输出为6
    t1.join();
    return 0;
}

1.2 类类型的实参

1.2.1 传递的是左值对象

class A {
private:
    int m_i;
public:
    A(int i) :m_i(i) { cout << "转换构造" <<std::this_thread::get_id()<<endl; }
    A(const A& a):m_i(a.m_i) {cout << "拷贝构造" <<std::this_thread::get_id()<< endl;}
     A(A&& a):m_i(a.m_i) { cout << "移动构造" << std::this_thread::get_id()<<endl;}
    ~A() {cout << "析构函数" <<std::this_thread::get_id()<< endl;}
};
 
void myPrint2(const A& a) 
{cout << "子线程参数地址是" <<&a<<std::this_thread::get_id()<< endl;}//4.子线程参数地址是0157D48049564
 
int main() {
    int i = 5;
    A myobj(i);//1.转换构造25964    6.析构函数25964
    cout << "主线程id是" <<std::this_thread::get_id()<< endl;//2.主线程id是25964
 
   thread mytobj(myPrint2,myobj); //3.拷贝构造25964       5.析构函数49564
                                 //分析一下为什么上面会调用拷贝构造
                                 //myobj是一个左值对象,因此调用拷贝构造来生
                                 //成一个副本放入tuple中。这个过程发生在主线程中
    mytobj.join();
 
    return 0;
}

1.2.2 传递的是临时对象(即右值对象)

class A {
...//定义与前面一样
};
 
void myPrint2(const A& a) //定义与前面一样
{...}    //4.子线程参数地址是00DED638 30492
 
int main() {
    int i = 5;
    cout << "主线程id是" <<std::this_thread::get_id()<< endl;//1.主线程id是33312
thread mytobj(myPrint2,A(i));//2.转换构造33312,3.移动构造33312 
                              //4.析构函数33312 5.析构函数30492
                               //首先,A(i)会调用转换构造生成一个临时对象
                                //随后对这个临时对象按值拷贝到thread中
                                // 由于临时对象是个右值,因此调用的是移动构造
                                //这两个构造都发生在主线程中
    mytobj.join();
 
    return 0;
}

关于临时对象还有种可能

class A {
...//定义与前面一样
};
 
void myPrint2(const A& a) //定义与前面一样
{...}   //4.子线程参数地址是00E7D800 28216
 
int main() {
    int i = 5;
   A a(i); //1.转换构造41312      6.析构函数41312
    cout << "主线程id是" <<std::this_thread::get_id()<< endl;//2.主线程id是41312
thread mytobj(myPrint2,std::move(a));//3.移动构造41312  5.析构函数28216
                              //4.析构函数33312 5.析构函数30492
                               //因为move(a)返回的是一个右值,会调用移动构造生成到thread的 
                                 //tuple中。同样的,这一步发生在主线程中
    mytobj.join();
 
    return 0;
}

1.2.3 传递的参数需要隐式类型转换

class A {
...//定义与前面一样
};
 
void myPrint2(const A& a) //定义与前面一样
{...}    //3.子线程参数地址是00FFF7E4 28552
 
int main() {
    int i = 5;
    cout << "主线程id是" <<std::this_thread::get_id()<< endl;//1.主线程id是50076
thread mytobj(myPrint2,i);//2.转换构造28552     4.析构函数28552
                          //分析:首先i按值传入副本到thread,其类型仍然是int,这一步发生在主线程
                          //随后,子线程调用move向线程函数传参时,发生int到A的隐式类型转换(调用 
                            /转换构造),这一步发生在子线程中
    mytobj.join();
 
    return 0;
}

需要说明的是,我看很多人认为如果调用detach的话,一旦主线程在子线程前面结束,那么i会被销毁,导致隐式类型转换时出错。我觉得这是错误的,因为在主线程中,已经生成了一个i的副本到thread的tuple中,就算主线程结束,i被销毁,但i的副本不会,除非是像前面提到的const char*类型的指针,因为指针和指针的副本都指向同一个内存块,一旦指针指向的主线程内存被销毁,那么指针副本指向的就是被销毁的内存,导致野指针,

1.2.4 传递的参数是指针

  void func(const string& s)
 { cout <<"子线程id是 " << std::this_thread::get_id() << endl; }
 
int main(){
 
  const char* name = "Santa Claus";
  
     thread t(func, &w, name); //ok。首先name在主线程中以const char*类型作为副本被保存
                              //在thread中,当向线程函数func传参时,会先将之前的name副本隐式转 
                               //换为string临时对象再调用move传给func的参数s
                             //同时要注意,这个隐式转换发生在子线程调用时,即在子线程中创建这个临 
                             // 时对象。这就需要确保主线程的生命周期长于子线程,否则name副本就会 
                             /变成野指针,从而无法正确构造出string对象。
    
  
 
    //std::thread t6(&Widget::func, &w, string(name)); //为避免上述的隐式转换可以带来的bug。可 
                                                      //以在主线程先构造好一个string临时对象, 
                                                      //再传入thread中。这样哪怕调用的是 
                                                     //detach,子线程也很安全
 
    t.join();  //如果这里改成t.detach,并且如果主线程生命期在这行结束时(意味着主线程在子线程前面 
               //完成运行),就可能发生野指针现象。
}

1.3 传入智能指针unique_ptr

智能指针其实也是个模板类,这里单独拿出来讲一下

void myPrint3(unique_ptr<A> pgn) {cout << myp.get() << endl;}//00E6BEB8
 
int main() {
 
 unique_ptr<int> myp(new int(100));
 
   thread mytobj(myPrint3,myp); //错误,首先unique_prt无法进行拷贝,只能移动。而myp是一个
                               //左值,不能对它进行移动构造产生一个副本放入thread
   thread mytobj(myPrint3,std::move(myp));//ok,std::move(myp)返回一个右值,因此调用移动构造产 
                                          //生一个副本放到thread中,这些都发生在主线程
    mytobj.join();
 
    return 0;
}

再者,讨论一下上述代码在使用detach时的情况。在此之前看下面代码

class B {
private:
    int m_b;
public:
    B(int b) :m_b(b) { cout << "转换构造" << endl; }
    ~B() { cout << "析构函数" << endl; }
};
 
void myPrint3(unique_ptr<B> pgn) { cout << pgn.get() << endl; }
 
int main() {
    unique_ptr<B> t1(new B(5));
    {
        unique_ptr<B> t2 = std::move(t1);
        cout << "时间点1" << endl;
    }
    cout << "时间点2" << endl;
    return 0;
}

输出结果:

转换构造
时间点1
析构函数
时间点2

这说明t1被销毁时不会调用类B的析构函数,也不会释放分配的堆区内存。因为t1所含的指针由于后面的move操作已经被置空了。t2退出作用域时自动销毁,调用类的析构函数,并释放堆区内存

回过头

 void myPrint3(unique_ptr<int> pgn) {cout << myp.get() << endl;}
 
int main(){
   unique_ptr<int> myp(new int(100));
 
   thread mytobj(myPrint3,std::move(myp)); 
                                         
    mytobj.detach();//即使主线程比子线程先结束,那么myp在销毁时也不会释放堆区内存
                   //此时pgn包含的指针指向那块堆区内存。
                   //那么pgn在c++运行时库中销毁时,会释放堆区内存,不会造成内存泄漏
                   //因此用detach也是安全的
}

到此这篇关于C++多线程传参的实现方法的文章就介绍到这了,更多相关C++多线程传参内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++多线程传参的实现方法

本文链接: https://www.lsjlt.com/news/210923.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++多线程传参的实现方法
    目录1.线程传参的过程1.1 内置类型的实参1.2 类类型的实参1.3 传入智能指针unique_ptr1.线程传参的过程 下面是thread的源代码 template< cl...
    99+
    2023-05-17
    C++多线程传参 C++ 线程传参
  • C#线程传递参数实现方法
    这篇文章主要讲解了“C#线程传递参数实现方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#线程传递参数实现方法”吧!C#线程传递参数的实现是如何进行的呢?那么这里我们使用MyThread...
    99+
    2023-06-17
  • c语言多线程传递参数的方法有哪些
    在C语言中,有以下几种方法可以在多线程中传递参数:1. 通过全局变量:可以定义一个全局变量,在主线程中设置其值,在子线程中读取其值。...
    99+
    2023-09-23
    c语言
  • qt多线程传递参数的方法是什么
    在Qt中,可以使用信号和槽机制来在多线程之间传递参数。下面是一种常见的方法:1. 定义一个包含参数的信号和槽函数:```cppcla...
    99+
    2023-09-27
    qt
  • python多线程传递参数的方法是什么
    在Python中,可以使用threading.Thread类来创建多线程,并通过构造函数的args参数传递参数给线程函数。 以下是一...
    99+
    2024-02-29
    python
  • qt多线程传递参数的方法有哪些
    在Qt中,有多种方法可以在多线程之间传递参数。以下是其中一些常用的方法:1. 构造函数传递参数:在创建新的线程对象时,通过构造函数将...
    99+
    2023-09-13
    qt
  • python之线程池map()方法传递多参数list
    目录线程池map()方法传递多参数list线程池并发需要引入模块他的语法为先看一下整体代码总结线程池map()方法传递多参数list 之前通过threading.thread()进行...
    99+
    2023-03-14
    python线程池map python线程池 线程池map传递多参数list
  • javascript多线程的实现方法
    本篇内容介绍了“javascript多线程的实现方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • C++ SOCKET多线程实现聊天小程序的方法
    本篇内容主要讲解“C++ SOCKET多线程实现聊天小程序的方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++ SOCKET多线程实现聊天小程序的方法”吧!本文实例为大家分享了C++ SO...
    99+
    2023-06-20
  • c语言实现多线程的方法有哪些
    C语言实现多线程的方法有以下几种:1. POSIX线程库:POSIX是一种标准接口,用于跨平台的线程编程。C语言中可以使用pthre...
    99+
    2023-08-18
    c语言
  • C#多线程的ResetAbort()方法
    一、简介 Abort方法可以通过跑出ThreadAbortException异常中止线程,而使用ResetAbort方法可以取消中止线程的操作,下面通过代码演示使用 ResetAbo...
    99+
    2024-04-02
  • C#多线程的Join()方法
    一、简介 Join方法主要是用来阻塞调用线程,直到某个线程终止或经过了指定时间为止。官方的解释比较乏味,通俗的说就是创建一个子线程,给它加了这个方法,其它线程就会暂停执行,直到这个线...
    99+
    2024-04-02
  • ElementUI中的el-dropdown传入多参数的实现方法
    最近工作中因为业务中按钮的增多,导致页面排版按钮过多,页面不够美观,用户体验不佳,于是想到利用el-dropdown做一个下拉按钮(把多个按钮整合到了一起,下拉实现) 但是El...
    99+
    2024-04-02
  • c++实现多线程的方式有哪些
    C++实现多线程的方式有以下几种:1. 使用C++11标准中的std::thread类:通过创建std::thread对象来创建新的...
    99+
    2023-09-21
    c++
  • delphi线程传递参数的方法是什么
    在Delphi中,可以通过以下方法传递参数给线程: 使用构造函数:可以在线程的构造函数中添加参数,然后在创建线程时传递参数。例如:...
    99+
    2023-10-27
    delphi
  • Java Runnable线程传参,实现让run访问参数
    目录Java Runnable线程传参,让run访问参数前言解决办法Runnable实现参数传递有以下的方法Java Runnable线程传参,让run访问参数 前言 做Androi...
    99+
    2024-04-02
  • C/C++ 原生API实现线程池的方法
    线程池有两个核心的概念,一个是任务队列,一个是工作线程队列。任务队列负责存放主线程需要处理的任务,工作线程队列其实是一个死循环,负责从任务队列中取出和运行任务,可以看成是一个生产者和...
    99+
    2024-04-02
  • python线程池ThreadPoolExecutor,传单个参数和多个参数方式
    目录python线程池ThreadPoolExecutor,传单个参数和多个参数这是线程池传单个参数的下面是传多个参数的python线程池传入多个参数 ThreadPoolExecu...
    99+
    2023-03-14
    python线程池 python ThreadPoolExecutor python传单个参数 python传多个参数
  • C#多线程之线程中止Abort()方法
    一、简介 Abort()方法用来终止线程,调用此方法强制停止正在执行的线程,它会抛出一个ThreadAbortException异常从而导致目标线程的终止。 二、代码 cla...
    99+
    2024-04-02
  • java多线程守护线程的实现方法是什么
    本篇内容介绍了“java多线程守护线程的实现方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!lass StopThread...
    99+
    2023-06-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作