赞
踩
目录
Linux系统中并没有真正意义上的多线程,因为linux内核中并没有为线程构建数据结构。它的线程使用进程来模拟的。
本文讲解多线程概念,后序还有其它知识博文进行补充。
现在再理解进程:进程是承担分配系统资源的基本实体
线程是进程里的一个执行流,CPU调度的基本单位是线程。就比如上图的一个tast_struct就是一个线程。它们共用一个进程地址空间。一个进程可以有一个或者多个执行流。
由于在Linux中没有真正意义上的线程,是用进程来模拟的,所以CPU调度一个线程,看到的还是一个PCB(task_struct)。但是要比传统的进程更加轻量化。task_struct表示的还是进程控制块。
线程的主要作用是:将一个进程的代码和数据分割成几个部分,通过几个执行流(线程)去执行部分代码和数据。所以它比传统的进程更加轻量化。
注意一个进程至少有一个线程,进程与该线程的关系是1:n的。
为什么线程要指向进程的同一个虚拟地址空间?因为透过进程虚拟地址空间,可以看到进程的大部分资源,可以将进程资源合理分配给每一个执行流,就形成了线程执行流。
Linux下虽然没有真正意义上的线程,但是在内核中还是有一些关于线程的数据结构,只是没有专门描述线程的数据结构。
一个处理器只能处理一个线程,如果线程数比可用处理器数多,会有较大的性能损失,会增加额外的同步和调度开销,而资源不变。
编写多线程时,可能因为共享了不该共享的变量,一个线程修改了该变量会影响另外一个线程。多线程之间变量时同一个变量,多进程之间变量不是同一个变量,写时拷贝。
进程时访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
线程是进程的执行分支,线程出现异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止该进程内的所有线程也就终止了。
比如:一个线程数显除0或者野指针操作,导致硬件CPU或者MMU出现异常,传给操作系统,操作系统就会发送信号给进程,终止进程。
不同:
相同:
进程的多个线程共享同一个进程地址空间,因此数据段和代码段都是共享的,如果定义一个函数,每个线程都可以调用。如果定义一个全局变量,每个进程都可以访问,并且访问到的是同一个。并且线程含共享进程的:
线程与进程的关系:
总结:进程强调独立,但是又不是绝对的独立,比如进程间通信。线程强调共享(共享进程的代码和数据),但是又不是绝对的共享,线程有自己的数据。
由于在Linux中没有真正的线程,所以系统没有提供接口(系统调用),需要用户自己来编写。但是我们有一个第三方库,供我们来对线程进行操作。
要使用这些库函数需要引入头文件<pthread.h>
链接这些线程函数库时要使用编译器命令"-lpthread"选项
为什么连接线程库要指明库名?标准库不用指明库名?
因为标准库是语言自带的,第三方库不是语言自带的,可能是系统或者是用户自己安装的,线程库是Linux系统安装的,不是语言提供的,对于gcc编译器来说是第三方库。gcc默认连接库是标准库(语言提供的)。编译器命令行参数中没有第三方库的名字。所以给编译器指明库名。
强调:找到库所在路径和使用该路径下的库文件,是两码事。找到路径找不到库,还需要指明库名。标准库中因为编译器命令行中有该库名。
创建线程库函数:
使用函数:
输出:
在Linux中由于没有真正的线程,目前的线程都是用原生线程库(Nagtive POSIX Thread Library)来实现。在这种实现下,线程又被称作轻量级进程,因为线程仍然使用进程描述符task_struct,但是只是执行进程的部分内容。
没有线程之前,一个进程对应内核的一个进程描述符,对应进程的ID。引入线程之后,一个进程对应了一个或者多个线程,每一个线程作为CPU调度的基本单位,在内核态也有自己的ID。
线程组,多线程的进程,又被称为线程组。每一个线程在内核中都存在一个进程描述符(task_struct),因为Linux下,用进程来模拟线程。进程结构体中的pid,表明上看是进程ID,其实不是,它实际对应线程ID,进程描述符中的tgid,对应用户层面的进程ID
总结:进程有自己的ID在源码中是tgid,线程也有自己的ID,在源码中是pid 。
进程ID有什么用呢?可以表示线程属于哪个进程的。就可以知道进程有多少线程
在创建线程使用的函数pthread_create的第一个参数返回的也是线程的id但是和这里的线程id,不同,这里的线程id是用来标识线程的,后面有介绍创建线程函数返回的id。
查看线程id:
代码使用的是上面的代码:
我们发现进程mythread有两个线程,一个线程的id是7854,一个线程的ID是7855。整个进程的ID是7854。
但是有一个线程的ID和进程的ID相同,这不是巧合。线程组(进程)里的第一个线程,在用户态被称为主线程,在内核中被称为group leader。线程中创建的第一个线程,会将该线程的ID设置成和线程组的ID相同。所以线程组内存在一个线程ID和进程ID相同,这个线程为线程组的主线程。
至于线程组的其它线程ID则由内核负责分配。线程组的ID总和主线程ID一致。
一个进程至少有一个线程。如果没有创建线程,该进程就是单线程的单进程。
注意:线程和进程不一样,进程由父子进程的概念,但是在线程了没有,所有进程都是对等的关系。
这里讨论的线程ID就是创建线程函数pthread_create的第一个参数,返回的线程ID。和上面讨论的线程ID不同。
上面讨论的线程ID(LWP)属于进程调度范畴。因为线程是轻量级进程,是操作系统调度的基本单位,所以会需要一个ID来标识给线程。
这里讨论的线程ID,是创建线程函数pthread_create的第一个参数。该内存是线程第三方库为线程在内存中开辟的一块空间。该线程ID指向该空间的起始地址。这个进程ID数据线程库的范畴,线程库的后序操作,就是根据该线程ID来操作的。
为什么返回的是起始地址?
由于Linux没有真正意义上的线程,线程管理需要线程库来做,线程库管理线程也是要先描述再组织,描述如图,组织程一个数组,再返回数组的起始地址。
可以通过函数查询当前线程ID。
Linux没有真正意义上的线程,Linux也没有为线程提供接口。为了管理线程,需要我们自己用户来编写。但是有一个第三方库,POSIX线程库给我们提供了管理线程的功能。但是线程需要内核来调度和执行。
线程管理是通过第三方库POSIX线程库来进行管理的,线管管理是介绍线程库是如何管理线程的。
新线程都是主线程创建的,线程之间的关系都是平等的。
前面有介绍
注意:主线程退出,整个进程就退出了。
只需要某个线程终止而不让进程终止,有三种方法:
新线程也可以用pthread_cancel终止主线程
pthread_exit函数:
注意使用return和pthread_exit返回的指针所指向的内存单元必须是全局或者是malloc分配的,不能是在线程函数栈上分配的,因为线程退出时,函数栈帧被释放了。
pthread_cancel函数
注意不能使用exit(),exit的作用是不论在哪里调用,终止进程。
线程为什么需要等待?
新线程都是主线程创建的,主线程需要知道新线程是否正常退出。
可以对比进程的等待。
默认以阻塞方式等待。
线程退出和进程退出一样,有三种状态。
1.代码正常运行,结果正确,正常退出。
2.代码正常运行,结果不正确,不正常退出。
3.代码出现异常,异常退出。
前两种情况以退出码来表述退出情况,后面一种以退出信号来表示。
但是线程等待函数的第2个参数返回的是执行函数的返回值,也就是退出码,没有表示线程异常退出的情况,这是为什么的?
因为某个线程如果运行异常终止,整个进程都会终止。进程异常终止,就属于进程的等待处理的范畴了。不属于线程范畴。比如:一个线程函数有除0操作,硬件MMU发现异常,操作系统收到异常,向该进程发出信号,终止进程。信号处理的单位是进程。
总的来说就是,等待线程只关心正常运行的退出情况,获取退出码。不关心异常退出情况,异常退出情况上升至进程处理范畴。
怎么拿到退出码的?
函数退出时,进程控制块(PCB)种有一个变量,保存退出码。
调用pthread_join函数的线程默认以阻塞方式等待线程id为thread参数的线程终止,线程以不同的方式终止,得到的终止状态不同
分别获取上面三种退出情况的退出码:
return
pthread_exit
pthread_cancel
注意:可以是线程组内其它线程对目标线程分离,也可以是线程分离自己。
创建新线程的线程,不关心新线程的返回值,可以使用线程分离。
但是线程虽然分离了,当分离的线程因为异常终止,依然会导致进程终止。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。