一个线程从什么中分离出来?下一个问题是 "什么是连接?"--与其让一个程序的语句从头到尾按顺序运行,不如把程序分成一些特殊的语句部分。这些特殊的部分被称为线程,然后它们可以平行或并发地运行。为了将一组语句转换为线程,需要进行特殊的编码。不幸的是,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()语句不再有正常的等待作用(阻塞调用线程),尽管它可能仍然在等待。不建议将被调用线程与调用线程分离。