C++多线程(2)——创建线程、线程传参

创建线程

#include<iostream>
#include<thread>

using namespace std;

void myPrint()
{
    
    
    cout<<"myPrint()线程开始执行"<<endl;
    cout<<"myPrint()线程执行完毕"<<endl;
    return;
}

class TA
{
    
    
public:
    TA()
    {
    
    
        cout<<"TA的构造函数"<<endl;
    }
    TA(const TA &ta)
    {
    
    
        cout<<"TA的拷贝构造函数"<<endl;
    }
    ~TA()
    {
    
    
        cout<<"TA的析构函数"<<endl;
    }
    void operator()()// 作为可调用对象必须重载()运算符
    {
    
    
        cout<<"operator()线程开始执行"<<endl;
    	cout<<"operator()线程执行完毕"<<endl;
    	return;
    }
    void thread_work()
    {
    
    
		cout<<"TA::thread_work()线程开始执行"<<endl;
        cout<<"TA::thread_work()线程执行完毕"<<endl;
        return;
    }
};

int main()
{
    
    
    // 1使用函数创建线程
    thread myThread1(myPrint);// 创建线程、线程执行入口、线程开始执行
    if(myThread1.joinable())
    {
    
    
        myThread1.join();// 阻塞主线程,让主线程等待子线程执行完毕,然后主线程和子线程汇合,主线程继续往下运行
    	//myThread1.detach();// 分离主线程,主线程运行结束将不再结束子线程,子线程将在后台继续运行
    }
    
    // 2使用可调用类对象创建线程
    TA ta;
    thread myThread2(ta);// 注意这里传值
    if(myThread2.joinable())
    {
    
    
        myThread2.join();
    	//myThread2.detach();
    }
    
    // 3使用类成员函数创建线程
    thread myThread3(&TA::thread_work, std::ref(ta));// 注意这里传引用
    if(myThread3.joinable())
    {
    
    
        myThread3.join();
    	//myThread.detach();
    }
    
    // 4使用lambda表达式创建线程
    auto lamThread=[]{
    
    
        cout<<"lamThread线程开始执行"<<endl;
    	cout<<"lamThread线程执行完毕"<<endl;
    };
    thread myThread4(lamThread);
    if(myThread4.joinable())
    {
    
    
        myThread4.join();
    	//myThread4.detach();
    }
    cout<<"hello world!"<<endl;
    return 0;
}

上面代码输出如下:

myPrint()线程开始执行
myPrint()线程执行完毕
TA的构造函数
TA的拷贝构造函数
operator()线程开始执行
operator()线程执行完毕
TA的析构函数
TA::thread_work()线程开始执行
TA::thread_work()线程执行完毕
lamThread线程开始执行
lamThread线程执行完毕
hello world!
TA的析构函数
  • 调用拷贝构造函数说明:类对象在初始化线程时,会被复制到线程中
  • 第一个析构函数是线程中被复制的TA对象的析构,第二个析构函数是主线程中的ta的析构
  • 使用std::ref(),可以真正实现引用的传递,并且减少了线程中对象的拷贝。

线程传参

传递临时对象作为线程参数

void myPrint(const int &i, char *pBuff)// 传递引用和指针
{
    
    
	cout<<i<<endl;
    cout<<pBuff<<endl;
    return;
}

int main()
{
    
    
    int mvar=1;
    int &mvary=mvar;
    char mBuff[]="this is a test";
    thread myThread(myPrint, mvar, mBuff);
    myThread.join();
    cout<<"hello world!"<<endl;
    return 0;
}
  • 实际上述代码存在问题:

    • mvar的地址=0x0022fa58,&mvary=0x0022fa58,而myPrint函数中i的地址=0x0072d9ec。也就是说,线程函数中的引用形参i实际上是值传递,并不是引用传递。
    • main函数中mBuff的地址=0x004ffb64,myPrint函数中pBuff的地址=0x004ffb64,说明线程函数中的指针形参与传递的实参指向同一块内存。
    • 若main函数中,线程被detach后,主线程运行结束,临时变量会被销毁,此时线程函数中的引用参数i由于是被复制进来的,仍然可以安全使用。但是指针参数pBuff则会变为野指针。
  • 将上述代码修改如下:

void myPrint(const int &i, const string &pBuff)// 这里得是const
{
    
    
	cout<<i<<endl;
    cout<<pBuff<<endl;
    return;
}

int main()
{
    
    
    int mvar=1;
    int &mvary=mvar;
    char mBuff[]="this is a test";
    thread myThread(myPrint, mvar, mBuff);
    myThread.join();
    cout<<"hello world!"<<endl;
    return 0;
}
  • 实际上上述代码仍然存在问题:

    • 事实上,main函数中的char mBuff在隐式转换为string时,有可能是在主线程运行结束后进行的,导致线程函数中的pBuff不一定是mBuff中的值。
  • 正确写法如下:

void myPrint(const int &i, const string &pBuff)
{
    
    
	cout<<i<<endl;
    cout<<pBuff<<endl;
    return;
}

int main()
{
    
    
    int mvar=1;
    int &mvary=mvar;
    char mBuff[]="this is a test";
    thread myThread(myPrint, mvar, string(mBuff));
    myThread.join();
    cout<<"hello world!"<<endl;
    return 0;
}
  • 因此要使用临时对象作为线程参数,避免隐式类型转换。证明如下:
class A
{
    
    
public:
    int m_i;
    A(int a) :m_i(a) {
    
     cout << "A的构造函数" << this << endl; }
    A(const A& a) :m_i(a.m_i) {
    
     cout << "A的拷贝构造函数" << this << endl; }
    ~A() {
    
     cout << "A的析构函数" << this << endl; }
};

void myPrint(const A& a)
{
    
    
    cout << a.m_i << endl;
    return;
}

int main()
{
    
    
    //thread myThread(myPrint, 10);// 使用隐式转换
    thread myThread(myPrint, A(10));// 使用临时对象
    myThread.detach();
    return 0;
}
  • 若使用隐式转换,则程序并没有输出A的构造函数,即并未在main函数结束前进行隐式转换,子线程不安全。

  • 若使用临时对象,则程序输出如下:

    A的构造函数004FF8A0
    A的拷贝构造函数008CD6F0
    A的析构函数004FF8A0
    

    表明,在main函数结束前,已经构造出临时对象。此外,还通过拷贝构造函数,将临时对象传递给了线程函数。传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。

总结
  1. 若传递int这种简单类型参数,建议值传递
  2. 如果传递类对象,避免隐式类型转换,应使用临时对象传参。且在线程函数中使用引用传递,避免二次拷贝。
  3. 建议不使用detach,使用join,避免局部变量失效,导致线程对内存的非法引用问题。

传递类对象、智能指针

class A
{
    
    
public:
    int m_i;
    A(int a) :m_i(a) {
    
     cout << "A的构造函数" << this << endl; }
    A(const A& a) :m_i(a.m_i) {
    
     cout << "A的拷贝构造函数" << this << endl; }
    ~A() {
    
     cout << "A的析构函数" << this << endl; }
};

void myPrint(A& a, shared_ptr<int> p)
{
    
    
    cout << a.m_i << endl;
    cout << *p << endl;
    return;
}

int main()
{
    
    
    A a(10);
    shared_ptr<int> p(new int(100));
    thread myThread(myPrint, std::ref(a), p);// 传递类的引用和智能指针
    myThread.join();
    //myThread.detach();
    return 0;
}
  • 使用std::ref(),可以真正实现引用的传递,并且减少了线程中对象的拷贝。

猜你喜欢

转载自blog.csdn.net/qq_34731182/article/details/113460673
今日推荐