进程的控制

进程的控制_第1张图片

文章目录

  • 1. Linux线程创建和等待
  • 2. 线程ID
  • 3. 线程的局部存储
  • 4. 分离线程

1. Linux线程创建和等待

进程的控制_第2张图片
这是前面写过的创建线程的代码,现在我们把线程的ID打印看一下:
进程的控制_第3张图片
我们可以看到线程的id特别大,这是为什么呢?我们后面再说。

如果我们想自己获取自己的线程ID,该怎么办呢
进程的控制_第4张图片
那么谁调用了这个函数,就获取谁的线程ID。

进程的控制_第5张图片
这里我们可以先写个打印函数,哪个线程对应的线程id我们打印出来。
进程的控制_第6张图片
运行结果如下:
进程的控制_第7张图片
可以看到每个线程的id不相同。

那么创建一个线程后我们也必须等待,不然可能会造成内存泄漏:
进程的控制_第8张图片

如果线程异常的该怎么办呢
进程的控制_第9张图片
我们在这个线程5秒之后出现异常,来看运行情况:
进程的控制_第10张图片
线程异常会导致整个进程异常,线程会影响到其它线程,它的健壮性降低。

pthread_join的第二个参数value_ptr的意义是什么
它是一个输出型参数,获取新线程退出时的退出码
1. 线程退出的方式,return
进程的控制_第11张图片因为返回值是void*的,所以我们需要先强转。
进程的控制_第12张图片
在打印ret,我们也需要强转成long long,不然会出现段错误。

运行结果如下:
进程的控制_第13张图片
从运行结果可以看出:可以成功获取线程的退出码。

我们知道:进程退出有3种方式,1.代码跑完,结果正确。2.代码跑完,结果不正确。3.异常退出。

但是线程退出不需要异常,因为线程异常等于进程异常。以后我们只考虑线程正常终止

2. 线程退出的方式,pthread_exit
进程的控制_第14张图片
进程的控制_第15张图片
3. 线程退出的方式,pthread_cancel
进程的控制_第16张图片
这里是获取线程的id来退出。

进程的控制_第17张图片
首先,我们让新线程死循环。
进程的控制_第18张图片
让主线程去取消新线程。
进程的控制_第19张图片
从运行结果我们可以看到:新线程也成功退出了。

为什么结果是-1呢
因为线程退出,是OS帮我们做的。它会帮我们在线程的task_struct里面修改对应的退出码

4. 线程退出的方式,以new的方式
进程的控制_第20张图片
我们把new出来的地址返回。
进程的控制_第21张图片
我们也可以在外面销毁空间。

运行结果如下:
进程的控制_第22张图片

2. 线程ID

从上面的结果我们可以看出线程的ID特别大,那么线程的ID到底是什么呢
它其实就是一个地址!我们知道:线程是一个独立的执行流,并且线程一定会在自己的运行过程中,产生临时数据(调用函数,定义局部变量等)

线程一定需要有自己的独立的栈结构
下面就谈一谈线程栈:
进程的控制_第23张图片
这里我们使用的线程库是用户级线程库,它是动态链接。

它的链接过程如下:
进程的控制_第24张图片
首先,我们先把自己的线程加载到物理内存,然后通过页表映射到代码区去执行。当我们执行到pthread_creat这种库函数时,发现没有,就会从磁盘中加载到物理内存,然后通过页表映射到共享区。
进程的控制_第25张图片
此时线程就会从代码区转到共享区。
进程的控制_第26张图片
在共享区执行完成后,再转到我们的代码区。

在代码区中,我们的自己的代码,调用库的代码,系统调用的代码,所有代码的执行都是在进程的地址空间当中执行的

下面我们来看一下库和OS的关系:
进程的控制_第27张图片
线程的全部实现,并没有全部体现在OS内,而是OS提供的执行流,具体的线程结构由库来进行管理

既然需要管理,库就会创建相关的结构体:struct thread_info,里面存了线程id和私有栈等等。那么库就会在共享区把我们的结构体信息的虚拟起始地址返回,所以pthread_t对应的就是用户级线程的控制结构体的起始地址。

Linux中,线程库用户级线程库,和内核的LWP是1:1关系

主线程的独立栈结构,用的就是地址空间中的栈区。新线程用的栈结构,用的是库中提供的栈结构

3. 线程的局部存储

下面讲解一个概念,线程的局部存储是什么
我们知道:我们定义的全局变量是默认被全部线程共享的,如果我们想让某个全局变量变成私有,我们就需要定义线程的局部存储

那么我们该如何定义
进程的控制_第28张图片
我们把每个线程的global_val的值和地址都打印出来,并且设置一个变量看它的变化。

运行结果如下:
进程的控制_第29张图片
可以看出:每个线程的global_val的地址都是一样的,并且每个执行流都对同一个global_val进行++。

如果我们想让这个全局变量变成每个线程的私有,我们可以这样:
在这里插入图片描述
在变量前面加上__thread(记住是两个_)。
进程的控制_第30张图片
每个线程的global_val的地址都不一样了,并且只对自己的++。

我们知道:每个线程id在内核中都有对应的轻量级进程LWP,这个线程的id是给我们用户自己用的,我们不需要用LWP,如果我们就想要LWP,我们就需要绕过这个pthread线程库。
进程的控制_第31张图片
我们需要调用这个系统调用,但是这个我们不能直接调用,直接调用会出错。
进程的控制_第32张图片
我们需要用这个系统调用。
进程的控制_第33张图片
运行结果:
在这里插入图片描述

4. 分离线程

默认情况下,新创建的线程是joinable的(意思是可以等待的),线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
pthread_join的作用是:1。释放线程资源,前提是线程退出了。2.获取线程对应的退出码

如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
在这里插入图片描述
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
在这里插入图片描述
joinable和分离是冲突的,一个线程不能既是joinable又是分离的

举个例子:
进程的控制_第34张图片
我们在每个自己的线程里给自己分离了。
进程的控制_第35张图片
然后我们把等待退出码再打印出来看一下。

运行结果如下:
进程的控制_第36张图片
我们可以看到线程分离后,线程还是在运行。
进程的控制_第37张图片
我们在这里加一个sleep看一下:
进程的控制_第38张图片
我们可以看到,出现错误了。

这是为什么呢
首先,我们要知道:CPU调度线程的运行是不确定的,一开始的情况是CPU先调度了主线程,然后去调度新线程,也就是说主线程先阻塞等待了,新线程还没来的及分离。
所以,我们更倾向于让主线程,分离其他线程

新线程分离,如果主线程先退出,那么进程就退出,新线程也就退出了。一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)

如何理解exit
进程的控制_第39张图片
我们在线程里面exit,看一下运行情况:
进程的控制_第40张图片
任何一个线程调用exit,都表示整个进程退出

你可能感兴趣的:(Linux,linux,线程)