目录
一、线程的理解
(1)什么是线程呢?
(2)线程的优缺点及异常
二、线程的基本操作
(1)创建一个新的进程
(2)获取线程id
(3)线程终止
(4)取消一个线程
(5)线程等待
(6)线程分离
三、gettid和pthread_self的区别
一、线程的理解
(1)什么是线程呢?
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列/一个执行流”,一切进程至少都有一个执行线程。
线程在进程内部运行,本质是在进程地址空间内运行
在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
从这张图中我们可以看到,Linux中其实是没有真正的线程的。Linux中的线程是对进程代码的复用,即在内核中复用进程pcb,作为线程的pcb(tcb),然后让这些pcb都指向一段相同的地址空间,再通过线程库的封装,给用户层营造了操作系统中的线程。
(2)线程的优缺点及异常
优点
缺点
异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
(3)线程和进程的一些差别
1.进程是资源分配的基本单位
2.线程是调度的基本单位
3.线程共享进程数据,但也拥有自己的一部分数据:
线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
二、线程的基本操作
(1)创建一个新的进程
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
(2)获取线程id
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
从这里的结果可以看到这个线程id是一个非常大的数,和我们之前所说的进程id完全不一样,进程id是操作系统中维护的一个id,二线程id是线程库维护的一个id,这个id其实就是该线程描述结构体在地址空间中的起始地址。
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
我们知道一个动态库被使用的时候需要被加载到进程地址空间的共享区中,pthread动态库也不例外。在创建一个新的线程的时候,操作系统首先会创建一个pcb结构体来描述该线程(这是内核中的描述结构体),但是对于用户而言,不能直接访问操作系统的内容,于是有了pthread这个软件层封装了操作系统的轻量级进程,给用户看到的直接就是线程。
而在pthread动态库中,他会维护一个类似于数组的结构,这个数组里面保存了所有线程的描述结构体tcb,里面包含了该线程的属性字段,而pthread_self函数返回的就是该描述结构体的地址!不同于操作系统内核的pid,当然这个描述结构体中一定保存了内核的pid来实现用户层和内核层的转换。
(3)线程终止
线程终止的函数就好像return一样使用,只不过要注意的是,return要返回一个void*的类型,因为我们在pthread_create的时候,他的返回值就是void*类型。
需要注意的是,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
(4)取消一个线程
既然能够创建一个线程,那么必然也能取消(销毁)一个线程,这个函数无论是主线程还是其他线程都可以调用,只需要传递该线程的pthread_t参数即可。
(5)线程等待
和进程等待一模一样,线程也需要被回收。因为Linux中的线程就是复用了进程的代码,也会产生pcb和库中的tcb,一旦没有回收,会造成内存泄漏的问题。
值得注意的是:这里的第二个参数是一个void**类型,是一个输入输出型参数。因为pthread_create的返回值是void*类型,要想对一个void*类型进行修改需要的参数是void**类型的指针。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,代码如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
5.线程的返回结果是由线程库来维护的,想要获取返回信息必须要用pthread_join函数。如果线程是分离的,则线程库不会维护其返回值,其他线程也无法通过线程库来获取其返回信息
(6)线程分离
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
一旦线程分离了,主线程再等待,返回值就不再是0
该函数可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。与线程终止函数使用无差。joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
三、gettid和pthread_self的区别
简单来说:gettid返回的是内核存储pcb的pid;而pthread_self返回的是线程库中的id(也就是描述结构体tcb的起始地址)。