0


【Linux】线程控制

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ Linux
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

目录

线程的异常终止

进程内部是可以存在多个线程的。那么如果有一个线程出现了异常(产生了信号)。那么会发生什么后果呢?我们用下面这段代码来验证一下。

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){int id =*(int*)args;delete(int*)args;int count  =0;while(1){printf("%d thread runing.... count = %d\n",id,count);//如果线程id为2, 且count计数到3,那么制造异常,产生信号 if(id ==2&& count ==3){int a =10;
      a /=0;}sleep(1);
    count++;}}intmain(){
  pthread_t tids[3];//创建三个线程for(int i =0; i <3; i  ++){int* id =newint(i);pthread_create(tids+i ,nullptr, ThreadRoutine,(void*)id);}while(1){printf("main thread runing...\n");sleep(2);}return0;}

该代码的逻辑就是创建3个线程,让第三个线程计数到3的时候 /0 产生信号。

我们运行看看结果。

在这里插入图片描述

我们发现整个进程都崩溃了。所以可以得出结论:

一个线程产生异常,那么所有线程都会崩溃。本质是因为所有线程共享信号处理函数,而信号来临时选择了默认处理方式,那么就会干掉整个进程。进程都被干掉了,那么内部的线程肯定也无一幸免。

线程等待

在创建进程的时候,我们的父进程要等待子进程。否则子进程在结束时资源将无法释放,成为僵尸进程,造成内存泄漏。而线程这里也是一样的道理,如果主线程不等待子线程。子线程结束后,主线程还在运行,那么一样会造成资源的泄漏。所以等待子线程是很有必要的。

线程等待函数:

#include<pthread.h>intpthread_join(pthread_t thread,void**retval);
第一个参数是要等待线程的tid 
第二个参数是创建线程时传入的线程执行函数的返回值,在内核中会回调那个函数。而那个函数的返回值会返回给内核,再由内核输出到这个参数上。
返回值:返回0为成功,非0为错误码。如果线程已分离还进行join,那么会返回-1。 

我们用以下代码来测试一下这个函数。

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){int id =*(int*)args;delete(int*)args;int count  =0;while(1){printf("%d thread runing.... count = %d\n",id,count);sleep(1);if(count++==3)break;}return(void*)id;//返回自己的id}intmain(){
  pthread_t tids[3];//创建三个线程for(int i =0; i <3; i  ++){int* id =newint(i);pthread_create(tids+i ,nullptr, ThreadRoutine,(void*)id);}//线程等待int* ids[3]={0};pthread_join(tids[0],(void**)ids+0);pthread_join(tids[1],(void**)ids+1);pthread_join(tids[2],(void**)ids+2);//打印三个线程的返回结果for(int i =0; i <3; i++)printf("thread %d : %ld\n",i,(longlong)(ids[i]));return0;}

这段代码的逻辑就是 创建三个线程,每个线程执行三次循环,随后返回传入时的id。主线程等待三个线程,等待结束后打印三个线程的返回值。

运行结果:

在这里插入图片描述

线程退出

如果我们在线程内调用exit函数,那么整个进程都会退出。因为exit是进程退出函数,无论在哪调用,都会导致进程退出。进程退出,那么所有的线程也会被释放。

所以线程退出,我们要用指定的退出函数。

线程退出函数:

#include<pthread.h>voidpthread_exit(void*retval);
参数是传入一个当前线程的返回值。

那么我们用代码来演示一下程序退出。

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){int id =*(int*)args;delete(int*)args;pthread_exit((void*)(id +10));int count  =0;while(1){printf("%d thread runing.... count = %d\n",id,count);sleep(1);if(count++==3)break;}return(void*)id;//返回自己的id}intmain(){
  pthread_t tids[3];//创建三个线程for(int i =0; i <3; i  ++){int* id =newint(i);pthread_create(tids+i ,nullptr, ThreadRoutine,(void*)id);}//线程等待int* ids[3]={0};pthread_join(tids[0],(void**)ids+0);pthread_join(tids[1],(void**)ids+1);pthread_join(tids[2],(void**)ids+2);//打印三个线程的返回结果for(int i =0; i <3; i++)printf("thread %d : %ld\n",i,(longlong)(ids[i]));return0;}

代码逻辑很简单,一进线程就退出,传入的值为 传入的id+10。

运行结果:

在这里插入图片描述

线程取消

线程退出函数必须要在要退出的线程内部执行。但如果我想在主线程中让指定的线程退出。那么我们可以用线程取消的函数。

线程取消函数:

#include<pthread.h>intpthread_cancel(pthread_t thread);
传入的是要取消的线程tid。
返回值为0则成功,失败返回一个错误的非0数字。

我们用一段代码来验证这个函数。

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){int count  =0;while(1){printf("%s runing.... count = %d\n",(char*)args,count);sleep(1);}returnnullptr;//返回自己的id}intmain(){
  pthread_t tid;pthread_create(&tid ,nullptr, ThreadRoutine,(void*)"new thread ");sleep(3);pthread_cancel(tid);//取消线程 sleep(3);return0;}

这段代码逻辑很简单,创建一个线程死循环执行。主线程三秒之后取消这个线程。再过三秒之后主线程结束(整个进程结束)。 在这个过程中我们实时监视。

监视结果:

在这里插入图片描述

我们发现三面之前这个进程有2个线程,三秒之后就变成了一个线程。说明创建的线程被取消了,而三秒之后一个线程也没有了,说明主线程执行完毕,进程退出了。

线程id

我们可以用pthread_self()来返回线程id,并分别用16进制和10进制打印这个id。因为我提前知道了这个id会是个很大数(透剧怪登)。

#include<pthread.h>
pthread_t pthread_self(void);

那么我们写段代码来打印一下线程的id。

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){int count  =0; 
  pthread_t tid =pthread_self();while(1){printf("%s runing.... count = %d ,thread id %ld , %lX \n",(char*)args,count,tid,tid);sleep(1);}returnnullptr;//返回自己的id}intmain(){
  pthread_t tid;pthread_create(&tid ,nullptr, ThreadRoutine,(void*)"new thread ");pthread_join(tid,nullptr);return0;}

这段代码的逻辑就是创建一个线程,然后通过pthread_self()函数获取当前线程的id,随后分别以10进制和16进制打印这个id。

运行结果:

在这里插入图片描述

当10进制打印进程id时,我们会发现它是一个很大的数字,并且看不出什么,但是当我们以16进制打印时。我们惊奇的发现,这是不是很像一个地址?

没错,线程id就是一个地址!

那么就有疑问了,CPU不是LWP进行调度吗?为什么线程id不是LWP,却是一个很大的数字?我们要搞清楚一点,pthread库是一个动态库!!而LWP是系统内核管理线程的标识。线程id是pthread库管理线程的标识。

在内核方面,用LWP来标识PCB进行调度,所以没有进程线程的区别。但是不要忘记了,线程是用pthread库创建出来的。那么创建出来要不要进行管理?这是必须的!!

pthread如何管理线程

首先我们都知道pthread是一个动态库,那么在程序执行的时候。会先把动态库加载进内存,随后根据页表把它映射到进程的共享空间内。

在这里插入图片描述

当pthread_create创建了一个线程时,那么就会在动态库中执行这个函数。随后就会创建一个struct pthread的结构体,线程局部存储以及线程栈。而这个结构体的地址(共享区中的虚拟地址)就是线程的id。这个结构体就是对这个线程的描述,存储着线程的信息,用来管理该线程。

在这里插入图片描述

虽然进程内部的线程都是共享进程地址空间的,那么就意味着进程地址空间中的栈(也叫主线程栈)可以被所有的线程访问。这是必然的,但访问归访问。线程本身的数据是不能存储在主线程栈中的。因为这样会导致存储十分混乱。也不能一个一个线程进行覆盖,这样会导致数据缺失。所以pthread动态库为每一个线程都提供了一个线程栈,这个线程栈是每个线程独有的(想跨线程访问也可以访问,但还是别这样做)。

线程局部存储是什么呢?

我们可以用__thread 来让一个全局变量变成线程私有的。

先看一段代码:

#include<iostream>#include<pthread.h>#include<unistd.h>int val =0;void*ThreadRoutine(void* args){while(1){printf("new thread , val = %d , &val = %p \n",val,&val);sleep(1);}returnnullptr;//返回自己的id}intmain(){
  pthread_t tid;pthread_create(&tid ,nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread , val = %d, &val = %p\n",val,&val);
    val++;sleep(1);}pthread_join(tid,nullptr);return0;}

这段代码的逻辑就是创建一个线程不断打印全局变量,主线程也不断打印全局变量,但是主线程会在每打印一次自增一次全局变量。

这段代码的结果是这样的:

在这里插入图片描述

没有意外,因为线程之间共享全局变量。所以打印的地址是一样的,值也是同步的。

那么我们把int val换成__thread int val再来试试效果。

#include<iostream>#include<pthread.h>#include<unistd.h>int val =0;void*ThreadRoutine(void* args){while(1){printf("new thread , val = %d , &val = %p \n",val,&val);sleep(1);}returnnullptr;//返回自己的id}intmain(){
  pthread_t tid;pthread_create(&tid ,nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread , val = %d, &val = %p\n",val,&val);
    val++;sleep(1);}pthread_join(tid,nullptr);return0;}

然后我们运行这段程序。

运行结果:

在这里插入图片描述

我们发现两个val的值不同步了,地址也不一样了。

原理很简单,本质就是在编译时,把val值的数据拷贝了一份。拷贝进了对应线程的线程局部存储中。

结论:

线程id本质是一个地址,这个地址在共享区中,与页表建立映射。最终映射到物理内存中对应的struct pthread结构体的地址起始处。在pthread_create创建了一个线程之后,pthread会在共享区创建一块内存。这个内存存储着线程的结构体,线程的局部存储以及线程栈。 而通过线程id可以找到线程结构体的起始位置。

线程分离

如果我们的主线程创建了线程之后却不想管它。那么我们可以用pthread_detach来让线程分离。 用pthread_detach来分离线程,那么被分离的线程在结束后就会自动销毁。如果不detach也不join,那么线程的一些资源就无法被释放,此时的线程就会陷入与僵尸进程相似的状态。

线程分离函数:

intpthread_detach(pthread_t thread);
参数是传入要分离线程的tid。
返回值:0为成功,非0则分离失败。

pthread_detach代码:

#include<iostream>#include<pthread.h>#include<unistd.h>void*ThreadRoutine(void* args){pthread_detach(pthread_self());int count =5;while(count--){printf("new thread , count = %d\n",count);sleep(1);}returnnullptr;//返回自己的id}intmain(){
  pthread_t tid;pthread_create(&tid ,nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread runing.... \n");sleep(1);}return0;}
标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/Lin5200000/article/details/133691159
版权归原作者 林 子 所有, 如有侵权,请联系我们删除。

“【Linux】线程控制”的评论:

还没有评论