2 线程启动、结束,创建线程方法,join、detach

2 线程启动、结束,创建线程方法,join、detach


2.1 范例演示线程运行的开始和结束

​ 每个程序至少有一个线程:执行main()函数的线程(为主线程),其余线程有其各自的入口函数。线程与主线程(线程以main()为入口函数的线程)同时运行。如同main()函数执行完会退出一样,当线程执行完入口函数后,线程也会退出。在为一个线程创建了一个std::thread对象后,需要等待这个线程结束;不过,线程需要先进行启动。下面就来启动线程。

  • 包含一个头文件thread

    #include <thread>
    
  • 初始函数要写

    void func() {
        cout << "子线程开始" << endl;
      	cout << "do something..." << endl;
      	cout << "子线程结束" << endl;
    }
    
  • main中写开始代码

    int main() {
      thread t(func);
      return 0;
    }
    

代码2-1-1

#include <iostream>
#include <thread>
using namespace std;
void func() {
    cout << "子线程开始" << endl;
  	cout << "do something..." << endl;
  	cout << "子线程结束" << endl;
}
int main() {
  thread t(func);
  return 0;
}

运行结果

如果主线程执行速度快于子线程的,将会是以下结果

libc++abi.dylib: terminating
Abort trap: 6

2.1.1 居然直接报错了,这是为什么呢?

​ 主线程执行完毕,thread对象消亡时(即代码中的t对象),如果thread是joinable的(associated应该翻译为可关联的,也就是joinable的) ,析构函数会调用terminate(),terminate()会调用abort(), abort()是非正常结束进程,不进行任何清理工作,直接终止程序,其典型实现是输出标准错误流(即cerr使用的错误流)。如果thread被调用过join()或者detach(),那它就不是joinable的,所以不会引发terminate,进程正常终止。

2.1.2 “秀儿”的解决方法

修改代码,在main()函数中加入while(1)

代码2-1-2

#include <iostream>
#include <thread>
using namespace std;
void func() {
    cout << "子线程开始" << endl;
  	cout << "do something..." << endl;
  	cout << "子线程结束" << endl;
}
int main() {
  thread t(func);
  while(1);
  return 0;
}

这样,子线程一定会先执行完成,主线程卡在死循环里~,让我们看看运行结果吧~

运行结果

子线程开始
do something...
子线程结束

这下终于不会报错,且打出了我们想要的结果,但这样的程序肯定不是我们所想要的。

2.1.3 如何解决报错问题,利用detach()

代码2-1-3

#include <iostream>
#include <thread>
using namespace std;
void func() {
    cout << "子线程开始" << endl;
  	cout << "do something..." << endl;
  	cout << "子线程结束" << endl;
}
int main() {
  thread t(func);
  t.detach();
  return 0;
}

运行结果

有时候这样

子线程开始
do something...
子线程结束

有时候啥也没有


2.1.5 有时候有,有时候无这是为什么呢?

​ 一旦detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行(主线程跟该子线程失去联系)这个子线程就相当于被C++运行时库接管了,当这个子线程执行完毕后,由运行时库负责清理该线程相关的资源。

2.1.6 怎么才能让程序正常运行,且输出想要的结果?利用join()

  • 有关join()的解释

    ​ 加入/汇合子线程,即阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,然后继续执行以下代码,如果主线程执行完毕,子线程没有执行完毕,这种程序是不合格的,写出来的程序也是不稳定的 一个书写良好的程序,应该是主线程,等待子线程执行完毕后,自己才能最终退出。

代码2-1-4

#include <iostream>
#include <thread>
using namespace std;
void func() {
    cout << "子线程开始" << endl;
  	cout << "do something..." << endl;
  	cout << "子线程结束" << endl;
}
int main() {
  thread t(func);
  t.join();
  return 0;
}

运行结果

子线程开始
do something...
子线程结束

2.1.7 如何判断线程是否能够join?利用joinable()

  • joinable()的解释

    ​ 判断是否可以成功调用join或者detach,返回true和false,true:可以join和detach,false:不可以join和detach,且detach的线程,不可以再次join,join过的线程也不可以join

2.2 其他创建线程的手法

2.2.1 用类,以及一个问题范例

如果用类作为一个参数传递给入口函数,类必须重载operator()构造函数,即向入口函数传递一个factor实例。

Attention!!

有件事需要注意,当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析”(C++’s most vexing parse)。如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。

thread t(A());

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

使用在前面命名函数对象的方式,或使用多组括号①,或使用新统一的初始化语法②,可以避免这个问题。

thread t((A()));  // 1
thread t{A()};    // 2

代码2-2-1

#include <iostream>
#include <thread>
using namespace std;

class A {
public:
    int &m_i;
    A(int &i):m_i(i) {
        cout << "A的构造函数" << endl;
    }
    void operator() () {
        cout << "线程开始" << endl;
        cout << "m_i的值为:" << this->m_i << endl; // 产生不可预料的结果
        cout << "线程结束" << endl;
    }
};

int main() {
    int num = 6;
    A a(num);
    thread t(a);
    t.detach();
    return 0;
}

运行结果

A的构造函数

2.2.2 为啥能安全地传递对象?

​ 虽然未打印出6的值,但我们的关注点并不是这个(线程在后台已经执行了打印的语句,但是主线程先执行完毕,所以看不到输出结果),而是a作为主线程的局部变量,当主线程执行完毕,a变量释放,按一般情况会出错,真的会出错吗?

​ 答案是:不会的~。为了进一步说明这个现象,在类中加入了拷贝构造函数和析构函数,并打印主线程对象的地址和子线程中this的值(即对象的地址)代码如下:

代码2-2-2

#include <iostream>
#include <thread>
using namespace std;

class A {
public:
    int &m_i;
    A(int &i):m_i(i) {
        cout << "A的构造函数" << endl;
      	cout << "this's addr is: " << this << endl;
    }
    A(const A& a):m_i(a.m_i) {
        cout << "A的拷贝构造函数" << " this's addr is: " << this << endl;
    }
    ~A() {
        cout << "A的析构函数" << " this's addr is: " << this << endl;
    }
    void operator() () {
        cout << "线程开始" << endl;
        cout << "m_i的值为:" << this->m_i << " this's addr is: " << this << endl; 
        cout << "线程结束" << endl;
    }
};

int main() {
    int num = 6;
  	cout << "num's addr is : " << &num << endl;
    A a(num);
    thread t(a);
    t.join(); // 方便观察
    return 0;
}

运行结果

A的构造函数
this's addr is: 0x7ffee0bb2710
a's addr is : 0x7ffee0bb2718
A的拷贝构造函数this's addr is: 0x7ffee0bb26f0
A的拷贝构造函数this's addr is: 0x7f8490402bd8
A的析构函数this's addr is: 0x7ffee0bb26f0
线程开始
m_i的值为:6 this's addr is: 0x7f8490402bd8
线程结束
A的析构函数this's addr is: 0x7f8490402bd8
A的析构函数this's addr is: 0x7ffee0bb2710

​ 主线程中a对象的地址为0x7ffee45d8718,最终执行输出对象的地址为0x7f8490402bd8,**结论:由主线程传递给子线程的对象实际上是被是被复制到线程中去的。

2.2.3 用lambda表达式

代码2-2-3

#include <iostream>
#include <thread>
using namespace std;

int main() {
    auto annoymous_func = [] {
        cout << "线程开始" << endl;
      	cout << "do something" << endl;
        cout << "线程结束" << endl;
    };
    thread t(annoymous_func);
    t.join();
    return 0;
}

运行结果

线程开始
do something...
线程结束
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章