如何在C++中如何分离一个线程?

324 阅读4分钟

一个线程从什么中分离出来?下一个问题是 "什么是连接?"--与其让一个程序的语句从头到尾按顺序运行,不如把程序分成一些特殊的语句部分。这些特殊的部分被称为线程,然后它们可以平行或并发地运行。为了将一组语句转换为线程,需要进行特殊的编码。不幸的是,C++中的线程如果没有被连接起来,就会独立运行。在这种情况下,第二个线程可能会在主线程结束后结束。这通常是不可取的。

为了实现任何连接,需要有两个线程。一个线程调用另一个线程。加入一个线程意味着,当调用的线程正在运行时,它将在一个位置停止,并等待被调用的线程完成其执行(到其结束),然后再继续自己的执行。在线程停止的位置,有一个连接表达式。这种停止被称为阻塞。

如果被调用线程完成的时间太长,而且可能已经完成了调用线程期望它做的事情,那么调用线程可以将其分离。在分离之后,如果被调用线程在调用线程之后完成,应该不会有问题。脱离意味着断开连接(链接)。

回顾一下

一个线程是一个顶层函数,它被封装成一个线程对象,从线程类中实例化出来。用顶层函数实例化线程意味着调用该函数。下面是一个简单的线程程序,其中有连接语句。

#include <iostream>

#include <thread>

using namespace std;


void func() {

        cout <<". . . from thread!" <<'\n';

    }  


int main()

{

    thread thd(func);

    thd.join();

    /* statements */


    return 0;

}

这里有两个线程:对象thd和main()函数。main函数就像主线程。请注意,这里包含了线程库。输出是:

    . . . from thread!

在命令提示符下,一个带有线程的C++20程序,应按如下命令进行,对于g++编译器来说。

    g++ -std=c++2a sample.cc -lpthread -o sample.exe

文章内容

detach() 语法

detach()的语法很简单,它是:

    threadObject.detach()

线程对象的这个成员函数返回void。 threadObject是其函数正在运行的线程对象。当一个线程的函数正在运行时,该线程被称为执行线程。

一个线程只有在被加入后才能被分离,否则,该线程就已经处于分离状态了。

在调用线程的主体中分离的模糊性

在下面的程序中,被调用线程在调用线程的主体中被分离。

 #include <iostream>

    #include <thread>

    #include <string>

    using namespace std;


    string globl = string("on earth!");


    void func(string st) {

        string fin = "Living " + st;

        cout <<fin <<endl;

    }  


    int main()

    {

        thread thr(func, globl);

        thr.join();

        thr.detach();


        return 0;

    }

作者的电脑在运行时的输出结果是

 Living on earth!

    terminate called after throwing an instance of 'std::system_error'

      what():  Invalid argument

    Aborted (core dumped)

预期的正确输出应该只是。

    Living on earth!

当一个线程结束其执行时,执行者会释放它所拥有的所有资源。当一个线程被加入时,调用线程的主体在这时会等待,直到被调用线程完成其执行,然后调用线程的主体继续自己的执行。

进一步输出的存在的问题是,尽管被调用线程可能已经完成了给它的任务,它的资源并没有全部被拿走,但detach()函数导致调用函数的主体继续执行。如果没有detach()函数,被调用的线程就会完成任务,再加上它的所有资源被拿走;而输出将是预期的简单单行。

为了进一步说服读者,请考虑以下程序,它与上述程序相同,但注释了join()和detach()语句。

    #include <iostream>

    #include <thread>

    #include <string>

    using namespace std;


    string globl = string("on earth!");


    void func(string st) {

        string fin = "Living " + st;

        cout <<fin <<endl;

    }  


    int main()

    {

        thread thr(func, globl);

        //thr.join();

        //thr.detach();


        return 0;

    }

作者电脑的输出结果是。

 terminate called without an active exception

 Aborted (core dumped)

main()函数没有等待线程做任何事情就跑到了终点。就这样,线程无法显示其输出。

全局范围内的线程名称

一个线程可以在全局范围内被实例化。下面的程序说明了这一点。

    #include <iostream>

    #include <thread>

    using namespace std;


    thread thr;


    void func() {

        cout <<"the first line" <<endl;

        cout <<"the second line" <<endl;

    }


    int main()

    {

        thr = thread(func);

        thr.join();


        return 0;

    }

输出的结果是

    the first line

    the second line

在程序中定义函数func()之前;有这样的语句。

thread thr;

语句,它实例化了线程thr。在这一点上,thr没有相应的函数。在main()函数中,第一条语句是。

        thr = thread(func);

这条语句的右侧创建了一个没有名字的线程,并将该线程分配给线程变量thr。通过这种方式,thr获得了一个函数。下一条语句是加入被调用的线程。

在被调用的线程中分离

一个更好的分离线程的方法是在被调用线程的主体中进行分离。在这种情况下,线程对象必须在全局范围内被创建,如上图所示。然后,detach语句将在被调用线程的主体中,在那里应该发生分离。下面的程序说明了这一点。

    #include <iostream>

    #include <thread>

    using namespace std;


    thread thr;


    void func() {

        cout <<"the first line" <<endl;

        thr.detach();

        cout <<"the second line" <<endl;

    }


    int main()

    {

        thr = thread(func);

        thr.join();

        cout <<"main() function line" <<endl;


        return 0;

    }

输出结果是

    the first line

    the second line

    main() function line  

在运行时没有发出错误信息。join()语句期望线程在main()函数体继续执行之前执行。尽管被调用的线程在执行过程中被分离了,但这种情况还是发生了,语句是这样写的。

        thr.detach();

就这样,main()函数(主线程)在被调用线程完成后继续执行,其所有资源被执行者释放。在被调用线程的后半部分,被调用线程已经被分离,尽管调用线程仍在等待。

程序开始时,包含了cout对象的iostream库。接下来,是加入线程库,这是必须的。然后是线程的实例化,there is the instantiation of the thread, thr, without a function.它将使用的函数就在后面定义。这个函数在其主体中包含了对象thr的分离声明。

在main()函数主体中,第一条语句创建了一个函数的线程,但没有名字。然后这个线程被分配给thr。因此,thr现在有了一个函数,好处是它是在全局范围内创建的,这样就可以在func()中看到它。

下一条语句将main()函数的函数体与被调用的线程连接起来。该线程是在main()函数的第一条语句中被调用的。此时,main()函数体等待着被调用的线程运行到终点,并释放其所有资源,尽管它在中间被分离了。只要被调用线程内部的任何东西是合法的,join()函数就会履行其职责。

因此,在被调用线程成功退出后,主函数继续执行,正如预期的那样(释放了所有资源)。这就是为什么

main() function line”

会在被调用线程的所有输出之后被输出。

结论

分离一个线程意味着被调用线程可以继续执行,而被调用线程也可以继续执行。也就是说,调用线程在加入后不再继续等待(阻塞)。这可以提高两个线程的速度,使它们能够并行运行,从而提高整个程序的速度。在这种情况下,最好是在线程的主体中分离出线程,它们之间不再发生通信。另外,为了达到这个目的,让线程变量在全局范围内创建,而不在其函数中。在C++程序的main()函数中,可以创建一个匿名线程,并将感兴趣的函数分配给线程变量。这一步调用线程函数,因此,调用线程。

所以,在detach语句之后,join()语句不再有正常的等待作用(阻塞调用线程),尽管它可能仍然在等待。不建议将被调用线程与调用线程分离。