Linux下线程的控制
创建线程
- 由于Linux下不可以像创建进程一样直接用命令创建线程,因此这里我们得先介绍一下POSLX线程库
POSLX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以
pthread_
打头的 - 要使用这些函数库,要通过引入头文件
<pthread.h>
- 链接这些线程函数库时要使用编译器命令的
-lpthread
选项
创建线程函数原型
- thread:该参数是一个指针,当线程创建成功时,用来返回创建的线程ID。
- attr:该参数用于指定线程的属性,NULL表示使用默认属性。
- start_routine:该参数为一个函数指针,指向线程创建后要调用的函数。这个被线程调用的函数也称为线程函数。
- arg:该参数指向传递给线程函数的参数。
线程创建成功时,pthread_create函数返回0,若不为0则说明创建线程失败。线程创建成功后,新创建的线程开始运行第三个参数所指向的函数,原来的线程继续运行。
了解创建线程的函数之后,我们接着来使用一下这个函数
我们来看一下执行结果
从执行结果中我们可以看到的确有两个线程在并发执行各自的代码,但是我们又怎么清楚的看到自己创建的新线程呢,这里我们来介绍一条命令ps -ajL
该命令可以帮助我们查看线程相关信息
我们可以看到前两个线程的PID是相同的但是不同的是LWP,那什么是LWP呢,其实LWP就是我们线程的ID,我们在之前也提过进程ID,进程ID是标识唯一的一个的进程的,同样LWP线程ID也是用来标识唯一的一个线程的。
用户态 | 系统调用 |
---|---|
线程ID | pid_t gettid(void); |
进程ID | pid_t getpid(void); |
了解了这些概念之后我们来修改一下我们的代码,获取一下我们的pthread_create的一个参数,这里说的线程ID和我们刚刚通过ps -ajL
命令获取的线程ID并不是同一个,前一个是因为线程操作系统调度器的最小单位,该线程ID用于唯一标识该线程的,而我们现在获取的pthread_create的一个参数指向的一个虚拟内存,该内存单元的地址即为新创建线程的ID
我们可以看到这串数字很大,我们将其转换为16进制看一下,我们看着个序列好像有点像一个地址,没错其实就是一个地址,而该地址就是我们线程的ID
线程ID即进程地址空间布局
- 这里再强调一次,我们通过pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。这个线程ID和前面说的LWP表示的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
我们刚刚将获取的的pthread_ create函数的第一个参数转换为一个16进制序列之后也发现它很像一个地址,其实对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
- pthread_exit函数
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
- pthread_cancel函数
线程等待
线程等待的原因其实和进程等待的原因相似,避免线程退出之后,其空间未被释放,从而导致内存泄漏的问题,很好理解,那我们介绍一下让线程等待的函数
- pthread_join函数
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
- 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数
了解了概念我们就来用一下这个函数
执行结果
线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
这里要注意一点就是:joinable和分离是冲突的,一个线程不能既是joinable又是分离的
还是接着用一下这个函数
我们看运行结果,和错误返回码,我们就知道刚刚强调的线程分离和线程等待时冲突的,一个线程是不可能又是分离又是等待的。
总结
这里我们讲了有关线程控制的相关函数,创建线程,终止线程,线程等待,线程分离等,这些函数都不难理解,主要还是得多练习才能够熟练的使用。