欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > linux线程

linux线程

2025/4/20 4:35:05 来源:https://blog.csdn.net/2301_76227083/article/details/143131251  浏览:    关键词:linux线程

目录

一、线程

1、线程的定义

2、如何理解进程给线程分配资源呢?

3、重新定义线程和进程

4、linux中是如何实现线程的?

5、linux线程周边概念

6、线程优点

7、线程缺点

8、线程异常

9、进程vs线程

二、线程控制

01、线程的使用

02、线程编译

03、查看线程ID

04、线程健壮性

05、函数重入

06、线程数据共享 

 07、pthread_t

08、线程回调参数

09、线程等待

10、线程退出

11、在谈线程回调参数

12、创建多线程

13、线程分离


一、线程

1、线程的定义

是进程内部的一个执行分支,线程的执行粒度,要比进程更细。当我们创建一个线程的时候,是将进程的一部分资源分配给线程,让线程去执行,因此,说线程是进程内部执行就是在进程地址空间执行。执行粒度更细的意思是,线程执行进程的一小部分代码,而进程执行所有代码,因此比进程更细。

2、如何理解进程给线程分配资源呢?

就是进程将自己的一部分地址空间和对应的页表分配给线程,让线程去执行对应的代码。本质上就是划分一部分地址空间给线程,让线程去执行对应的代码数据。

3、重新定义线程和进程

如果站在内核的视角来看,线程是操作系统调度的基本单位,cpu在调度的时候都是以线程为单位进行调度的,那么进程呢?进程是承担分配资源的基本实体,操作系统在分配资源的时候都是以进程为单位来分配资源的。线程和进程的关系是,线程是进程的一个执行流资源。进程是线程是1:N的,一个进程内部都是有多个线程的,最少也要有一个线程。

4、linux中是如何实现线程的?

一个进程里面可以创建多个线程,那多个线程需不需要管理呢?线程的切换,线程的调度要不要做呢?当然要,那么需要定义一个新的结构来管理吗?linux的大佬们一看,线程和进程相比,无非就是拥有的资源少一点,执行的代码少一点吗?那直接把进程的那一套拿过来修改一下不就可以了吗?因此,linux中没有真正意义上的线程,而是用进程"模拟"的线程,模拟的线程也叫做轻量级进程。

5、linux线程周边概念

(1)线程比进程要轻量化(为什么?)

  a.创建和释放要轻量化(线程的生死问题)

进程创建需要PCB、地址空间、页表,几乎所有分配资源的操作都要做。

线程只需要创建PCB,释放只需要把PCB干掉就行。

  b.切换更加轻量化(运行)

进程在切换的时候地址空间、页表都需要切换,而线程不需要,线程只需要局部切换,因此效率更高。

在计算机中有局部性原理,就是说加载数据的时候每一次都会多给你加载一些,因为当你访问一部分数据时,有很大概率需要访问后面的数据

CPU中有一个cache寄存器,用来缓存热数据,在访问代码的时候会把后面的代码也预先加载到cache中,进程在切换调度的时候cache是要被切换的,不同的进程cache肯定是不同的,切换之后需要重新由冷变热加载到缓存里,需要浪费不少时间。但是线程不然,线程切换的时候虽然上下文会发生变化,但是cache是不会变的或者做少量的变动,因为这些线程是同一个进程里的, 它们的很多属性都是共享的,因此线程切换的效率更高的原因主要是因为它不需要重新加载缓存。

6、线程优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间切换相比,线程之间切换操作系统需要做的工作少很多
  • 线程占用的资源比进程少
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作

7、线程缺点

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变

  • 健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

8、线程异常

  • 单个线程如果出现除零、野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的分支,线程出现异常,进程也会出现异常,进而触发信号机制,终止进程、进程终止,该进程内的所有线程也会退出。

9、进程vs线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:
  1.         线程ID
  2.         一组寄存器(上下文)
  3.         栈
  4.         errno
  5.         信号屏蔽字调度优先级
  6.         调度优先级

线程虽然共享大部分资源,但是每个线程也要有自己的栈和上下文。独立的上下文,确保线程是被独立调度的,独立的栈结构确保执行流之间不会出现错乱。


进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表 不同的线程都指向同一个文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

二、线程控制

内核中有没有明确的线程概念呢?是没有的,只有轻量级进程的概念,因此操作系统不会给我们提供线程的系统调用,只会给我们提供轻量级进程的系统调用。但是我们要用的是线程呀,因此一些Linux大佬就在轻量级进程的系统调用的基础上在应用层封装了一个线程库——pthread库,这个库几乎所有的linux平台,都是默认自带这个库的。我们在linux中编写多线程代码需要使用pthread库。

01、线程的使用

线程创建接口:

使用线程需要包含 pthread.h头文件。我们如何创建一个线程呢?要使用pthread_create。

pthread_create参数:

(1)pthread_t *thread:pthread_t 变量是一个线程的ID,是一个整形,库给我们封装好了,直接使用即可,线程的ID方便我们后续进行线程的控制。

(2)const pthread_attr_t *attr: 这个参数是线程的属性,我们直接传递nullptr即可。

(3)void* (*start_routine) (void *):它是一个返回值为void*,参数也是一个void*的函数指针,需要传递一个函数进来,它的作用就是当我们进行线程创建的时候,我们想让线程执行一部分代码,那么我们要让线程执行那一部分代码呢?我们可以把要执行的入口函数传递进来,此时该线程就会执行这一部分代码。为什么返回值和参数要是void*呢?是因为void*可以接收和返回任意的指针类型,C语言是没有模版的,因此就使用void*来代替。

(4)void* arg:创建线程成功,新线程回调线程函数的时候,需要参数,这个参数就是给线程函数传递的,也就是作为第三个回调函数的参数。如果回调函数不需要参数,传nullptr即可。

pthread_create返回值:

如果线程创建成功返回0,否则返回出错的错误码。

创建一个线程

当我们创建一个新线程之后,新线程去从上往下执行它的执行流,如果不结束,这个线程永远执行,如果结束了,线程也就结束了,而主线程继续向后执行。

02、线程编译

当我们直接用makefile去编译的时候是编译不了的,这是因为链接的时候找不到pthread库,这也说明了我们用的这个不是系统调用,

因此在编译的时候需要加上-lpthread,此时就可以编译成功了 

我们运行一下看一下结果 

我们可是写了两个死循环哦,但我们发现他们都在跑,如果只有一个执行流是不可能同时执行的,因此这必然是两个执行流 ,而且它们打出来的pid还都是一样的,这也说明这两个线程是同一个进程内部的。

03、查看线程ID

ps-aL:查看线程id

我们能查到进程pid,能不能查看所有的线程呢?可以使用 ps-aL。L可以理解为light,轻的意思。

PID我们知道是进程ID,那么LWP是什么呢 ,进程需要有标识符,那么轻量级进程也要有对应的标识符,LWP叫做light weight process,这就是轻量级进程的PID。因此,操作系统在调度的时候跟本不看PID,而是看LWP,而且我们会发现,第一个的PID是等于LWP的,说明这个线程是主线程,最先有的线程是它,剩下的这个线程LWP是1499463不等于PID,说明它是后面创建出来的,操作系统可以根据PID是否等于LWP判定该线程是否是主线程。

04、线程健壮性

在上述执行的代码中,当我们干掉任何一个线程,整个进程都会被干掉,那么这个信号是发给进程还是线程的呢?我们认为是发给进程的,毕竟线程是在进程内部的,因此,正如上面所谈的,线程的健壮性是很差的。包括当一个线程发生什么除零错误等异常的时候,也是会把整个进程干掉的。

05、函数重入

函数重入的意思是一个函数被多个执行流同时执行。

在上面这段代码中,主线程和新线程都执行了show函数,该函数就被重入了。

#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>using namespace std;
// new thread// 可以被多个执行流同时执行,show函数被重入了
void show(const string& name)
{cout << name << "say# " << "hello thread" << endl;
}
void* threadRoutine(void* argc)
{while(1){// printf("new thread, pid: %d\n", getpid());show("[new thread]");sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr); //不是系统调用while(true){// printf("main thread, pid: %d\n", getpid());show("[main thread]");sleep(1);}return 0;
}

06、线程数据共享 

我们定义一个全局变量,让主线程和新线程打印这个变量的值和地址,并让新线程对该变量做递增。我们会发现,打印的变量地址和值都是相同的。这也说明线程之间的共享是非常容易的。但是这样的操作其实是有安全问题的,后面再谈。

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdio>
#include <pthread.h>using namespace std;int g_val = 100;// 可以被多个执行流同时执行,show函数被重入了
void show(const string& name)
{cout << name << "say# " << "hello thread" << endl;
}
void* threadRoutine(void* argc)
{while(1){printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);// printf("new thread, pid: %d\n", getpid());// show("[new thread]");g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while(true){printf("main thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);// printf("main thread, pid: %d\n", getpid());//show("[main thread]");sleep(1);}return 0;
}

 07、pthread_t

线程创建已经说完了,但是我们还是比较好奇创建线程的第一个参数pthread_t是多少,前面我们说是一个整数,我们打印出来看看 ,我们可以看到tid是一个非常大的数字,难道这个tid不应该是之前的LWP吗,那这打出来的是啥呢 ,这里我们使用无符号长整数的方式打印的,我们使用%p打印出来看看它像什么,它特别像一个堆栈之间的一个地址。

无符号长整数方式打印

%p方式打印

前面我们说过,linux是没有线程的概念的,只有轻量级进程的概念,因此操作系统只有轻量级进程的调用而没有线程的系统调用,因此线程库是用轻量级进程的系统调用封装的,那么使用哪个系统调用封装的呢?使用下图的clone,这个系统调用封装的,我们可以看到,它的第一个参数是一个回调参数,第二个参数是一个自定义的栈空间。因此要封装的话,在线程库里每一个线程都要有一个回调函数,也要有独立的栈空间,回调函数就是我们外面create传递的 ,也就是说,线程的概念是库给我们维护的,所以,当我们写线程代码的时候,库要不要加载到内存里呢?当然是要的,它加载到哪里呢?加载到内存的共享区中,因此,每一个创建好的线程都要开辟一块空间充当栈空间,那么线程的回调函数、栈空间、线程的调度、线程的状态,操作系统知不知道,操作系统不知道,但是线程库是要维护的,主要维护线程的概念,不用维护线程的执行流!线程相关的属性由库来维护,那么线程库里同时存在多个被创建的线程是很正常,所以,线程库注定要维护多个线程属性,那么这些线程要不要被管理呢?当然要,先描述在组织。因此,每创建一个线程,在线程库里都会创建一个tcb线程控制块,里面是线程的LWP指向的执行流、以及各种属性。

那么tid是什么呢?是每一个线程的库级别的tcb的起始地址,有了起始地址,我们想要找到哪个线程的属性 直接就可以获取到。

每一个线程都要有自己独立的栈结构,因为每个线程都有自己独立的调用链,函调函数里面还可以去调用另外一个函数。栈结构会保存函数的返回值、参数、临时变量等。

而主线程的栈结构直接使用地址空间的栈结构即可。剩下被创建的线程都叫做轻量级进程,首先在库里为新线程里创建tcb线程控制块,起始地址叫做线程的ID,在tcb里有一块默认大小的空间叫做线程栈,接着要创建执行流了,在库中调用clone,把线程栈传递给clone,因此, clone执行流在调用时形成的临时数据都会被压入到应用层tcb的栈结构当中,因此,除了主线程外,其他所有线程的独立栈,都在共享区具体来说实在pthread库中,tid指向的用户tcb中!这样各个线程就不会互相干扰了。

LWP是操作系统在底层维护轻量级进程的执行流要做的工作,而tid是线程库帮我们维护出来的一个虚拟地址。因此LWP和tid是1比1的关系。

08、线程回调参数

我们给线程的回调函数传递一个参数"thread 1",我们发现果然接收到了传递来的参数,传递来的参数需要强制类型转换成对应的类型。

void* threadRoutine(void* argc)
{const char* name = (const char*)argc;while(1){printf("%s, g_val: %d, &g_val: 0x%p\n", name, g_val, &g_val);sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(true){sleep(1);}return 0;
}

09、线程等待

(1)pthread_join

主线程还是新线程先运行完全由调度器说了算,但是谁应该最后退出呢?是主线程,谁让主线程创建了新线程呢?创建线程的本质就是对线程做管理,线程退出的时候主线程需要等待,如果不等待会造成资源泄露的问题,更重要的是,我们创建线程是让线程给我办事的,线程把事情办的怎么样,我怎么知道?那我还要不要在办一次呢?因此需要线程把退出结果给我。因此,线程等待的原因第一点是避免资源泄露,第二点是如果我们需要可以获取一下线程退出的返回结果。那么如何等待呢?用pthread_join等待。

 pthread_join第一个参数是pthread_t的类型,第二个参数暂时先不管,直接传nullptr即可。成功返回0,失败返回错误码。

void* threadRoutine(void* argc)
{int cnt = 5;const char* name = (const char*)argc;while(1){printf("%s, g_val: %d, &g_val: 0x%p\n", name, g_val, &g_val);// printf("new thread, pid: %d\n", getpid());sleep(1);cnt--;if(cnt == 0) break;}return nullptr; // 走到这里线程默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");pthread_join(tid, nullptr); // main thread等待的时候,默认是阻塞等待的cout << "main thread quit..." << endl;return 0;
}

主线程会一直等待线程的退出,直到线程退出了主线程才会退出。

(2)获取线程退出结果

我们可以用pthread_join进行等待了,但是现在问题是,一个线程执行完毕,结果怎么样我怎么知道呢?我们是通过线程的返回值获得的 。此时就需要用到pthread_join的第二个参数。我们需要再外面定义一个void*类型的变量,然后将该变量的地址传过去,这个地方是二级指针。

 

我们可以看到主线程拿到了新线程的退出结果"ok",虽然是void*类型,但是我们把它当做char*,直接强转即可。 

(3)线程异常

代码跑完有三种情况,结果对,结果不对,异常。那我们不需要考虑线程退出异常的情况吗?完全不需要,因为做不到,如果线程都异常了,那么整个进程就会挂掉。

10、线程退出

线程除了执行完毕代码后会自动退出,还有没有其它退出线程的方法呢,比如自己写个exit把线程退出了。

(1)exit退出

我们直接在线程代码里循环五次,然后直接调用exit,但可惜,exit是不能用来退出线程的。

void* threadRoutine(void* argc)
{int cnt = 5;const char* name = (const char*)argc;while(1){printf("%s, g_val: %d, &g_val: 0x%p\n", name, g_val, &g_val);// printf("new thread, pid: %d\n", getpid());sleep(1);cnt--;if(cnt == 0) break;}exit(11); //直接调用exit,exit是用来终止进程的,不能用来终止线程return (void*)"ok"; // 走到这里线程默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");void* ret;pthread_join(tid, &ret);cout << "new thread quitcode: " << (char*)ret << endl; cout << "main thread quit..." << endl;return 0;
}

我们发现,当线程执行exit之后,主线程后续的代码并没有打印出来,这说明整个进程都挂掉了,因此我们可以得出结论,exit是退出进程的,不能用来退出线程。

(2)pthread_exit退出

它是库里提供的一个退出线程的函数,参数是线程要返回的结果。 

void* threadRoutine(void* argc)
{int cnt = 5;const char* name = (const char*)argc;while(1){printf("%s, g_val: %d, &g_val: 0x%p\n", name, g_val, &g_val);sleep(1);cnt--;if(cnt == 0) break;}pthread_exit((void*)"very good");//exit(11); //直接调用exit,exit是用来终止进程的,不能用来终止线程return (void*)"ok"; // 走到这里线程默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");void* ret;pthread_join(tid, &ret);cout << "new thread quitcode: " << (char*)ret << endl; cout << "main thread quit..." << endl;return 0;
}

 

可以看到,调用pthread_exit,可以成功退出线程,并且拿到线程的返回结果。

 除了pthread_exit和默认执行完毕退出,还可以直接取消线程。

(3)pthread_cancel取消

 取消掉某一个线程。返回值是线程退出的结果。在下面这段代码中,主线程刚创建好一个线程,等了1秒直接将该线程取消掉。

void* threadRoutine(void* argc)
{int cnt = 5;const char* name = (const char*)argc;while(1){printf("%s, g_val: %d, &g_val: 0x%p\n", name, g_val, &g_val);sleep(1);cnt--;if(cnt == 0) break;}pthread_exit((void*)"very good");//exit(11); //直接调用exit,exit是用来终止进程的,不能用来终止线程return (void*)"ok"; // 走到这里线程默认退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");// 创建完毕线程直接取消sleep(1); //为了保证线程创建完毕pthread_cancel(tid);void* ret;pthread_join(tid, &ret);cout << "new thread quitcode: " << (long long int)ret << endl; cout << "main thread quit..." << endl;return 0;
}

我们可以看到线程退出的结果为-1,如果线程被取消,会返回PTHREAD_CANCELED,它是一个宏,结果就是-1,不需要我们自己返回,pthread_join会到拿到-1.

11、在谈线程回调参数

传参的时候不止可以传递一个什么字符串数字哦,也可以传递我们自定义的对象类型。下面是创建一个线程处理求和请求的代码,然后新线程将计算结果返回给主线程。

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdio>
#include <pthread.h>using namespace std;class Request
{
public:// 构造函数Request(int start, int end, const string& threadName):_start(start),_end(end),_threadName(threadName){}// 计算结果返回int sum(){int ret = 0;for(int i = _start; i <= _end; i++){ret += i;cout << _threadName << "is running... current result: " << ret << endl;}return ret;}
private:int _start; // 表示计算的起始范围int _end;string _threadName; // 线程名
};class Response
{
public:Response(int result, int exitcode):_result(result),_exitcode(exitcode){}
public:int _result;   // 计算结果int _exitcode; // 计算结果是否可靠
};void* sumCount(void* arg)
{// 对arg做强转Request* req = static_cast<Request*>(arg);int sum = req->sum();// 构造一个响应Response* rsp = new Response(sum, 0);// 将响应返回return rsp;
}int main()
{pthread_t tid;// new 一个请求Request* req = new Request(1, 100, "thread 1"); //计算1 - 100求和pthread_create(&tid, nullptr, sumCount, req); void* ret;pthread_join(tid, &ret);Response* rsp = (Response*)ret;cout << "rsp->result: " << rsp->_result << ", exitcode: " << rsp->_exitcode << endl; return 0;
}

在上述代码中,主线程new了一个请求传递给新线程,新线程也new了一个响应返回给主线程,也说明线程的堆空间也是所有共享的。 

12、创建多线程

#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <cstdio>
#include <pthread.h>using namespace std;struct threadData
{string threadname;
};int val = 0;
int* ptr = nullptr;
__thread int g_val = 0;void InitThreadData(threadData* td, int number)
{td->threadname = "thread" + to_string(number);
}// 3个线程执行的都是同一个函数哦
void* threadRoutine(void* arg)
{int test_i = 0;threadData* td =  static_cast<threadData*>(arg);// 如果是第二个线程,就把第二个线程的test_i变量的地址给ptr。int i = 0;while(i < 5){//  cout << "pid: " << getpid() <<//      ", threadname: " << td->threadname << ", test_i: " << test_i << //      ", &test_i: " << &test_i << ", val: " << val << ", &val: " << &val //      << endl;//    cout << "pid: " << getpid() <<", threadname: " << td->threadname //         << ", test_i: " << test_i << ", &test_i: " << &test_i << ", val: " //         << val << ", &val: " << &val << endl;cout << "pid: " << getpid() <<", threadname: " << td->threadname << ", test_i: " << test_i << ", &test_i: " << &test_i << ", g_val: " << g_val << ", &g_val: " << &g_val << endl;sleep(1);test_i++;i++;//val++;g_val++;}if(td->threadname == "thread2") ptr = &test_i;return nullptr;
}int main()
{// 创建多线程vector<pthread_t> tids;for(int i = 0; i < 3; i++){pthread_t tid;threadData* td = new threadData(); InitThreadData(td, i);pthread_create(&tid, nullptr, threadRoutine, td);tids.push_back(tid);}sleep(1); // 确保赋值能成功for(int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}cout << "main thread, ptr: " << *ptr << ", &ptr: " << ptr << endl;return 0;
}

(1)堆空间共享

在上述代码中,threadData都是主线程new出来的td然后传递给新线程的,新线程可以获取到td,这说明了堆空间是线程间共享的。

(2)独立的栈结构

这3个线程执行的是同一个函数哦,而且定义了变量test_i,那么每个线程的test_i打印出来的值和地址是否相同呢?

答案是不同的,这也说明了每个线程都有自己独立的栈结构。  

主线程可以拿到其他线程栈区里的变量吗?可以的。在上述代码中,我们定义了一个全局的指针ptr,在线程执行的函数中,如果是第二个线程,就把第二个线程的test_i,赋值给ptr,最后主线程打印出来的ptr的地址果然和第二个线程的变量test_i地址相同,因此,线程和线程之间是没有秘密的,线程的栈上的数据,可以被其他线程拿到的。

(3)全局变量

上述代码中,我们定义了全局变量val,我们可以看到全局变量是可以被所有的线程看到的,那么这个全局变量叫什么呢?叫做共享资源。但是,如果我线程想要一个私有的全局变量呢?

 (4)私有全局变量

如果我们线程想要一个私有的全局变量,可以在全局变量前面加上__thread。我们可以看到访问的是同一个全局变量,但是打印的地址是不同的,这种技术叫做线程的局部存储。__是编译器提供的一个编译选项,默认编译的时候就把g_val给每个线程单独开辟一份,而且刚刚定义的全局变量地址是0x55...一看就是全局区的地址,但是此时的地址是0x7f...哦,这一看就是堆栈之间的地址,它和tid的地址不是一样的吗?说明该变量存储在共享区中。并且,线程的局部存储只能定义内置类型,自定义类型不太行。

 那它有啥用呢?我们如果要频繁调用一些属性函数可以使用它,避免多次调用系统调用。

13、线程分离

默认情况下,新创建的线程是需要等待的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,造成资源泄露。

如果我们不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出的时候,自动释放线程资源,主线程可以腾出手来做其他事情,不用对其进行等待。

这就是线程分离的系统调用,参数非常简单,你要分离那个线程就传哪个线程tid。

那么谁来分离线程呢?可以由主线程自己分离,也可以由该线程自己分离自己。

 主线程分离:

我们创建3个线程,然后将线程分离,之后在等待线程,线程已经被分离,此时如果等待会出错,我们看出错的结果是无效的参数。因此,线程被分离后就不能join了,如果我们要把线程分离,要保证主线程是最后一个退出的。

线程自己分离:

直接在线程的执行代码中加pthread_detach即可,pthread_self可以获取自己的tid。

void* threadRoutine(void* arg)
{pthread_detach(pthread_self()); // 线程自己分离自己int i = 0;while(i < 5){i++;}return nullptr;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词