Linux线程
线程框架
一、线程概念
线程的概念:
轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程。linux下,线程是最小的执行单位,进程是最小的系统资源分配单位。
线程和进程的区别:
区别:在于是否共享地址空间。 独居(进程);合租(线程)。
进程:有独立的地址空间,拥有PCB
线程:也有PCB,但没有独立的地址空间(共享)
线程是公用进程的空间的,那么可以理解为,在内存上,除了栈(stack)这块,其他地方都是共享的!(下图所示),这是因为线程是有自己的执行目的的,每个线程的任务是非常明确的,后面会提到,一个线程实际上就是执行的一个函数,因为函数是存储在栈中的,所以这块是没法共享的,要进行区分,除此之外,其他区域都是可以共享的。
对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
但!线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。
如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
二、线程共享资源和非共享资源
线程共享资源:
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源:
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量(每个线程有自己独有的errno变量)
- 信号屏蔽字
- 调度优先级(可以设置线程的优先级)
三、线程的优缺点
优点:
- 提高程序并发性(为了更好的利用CPU)
- 开销小(不必再申请空间,直接用的是进程的空间)
- 数据通信、共享数据方便(在一个进程中的线程,可以共享进程中所创建的变量来共用)
缺点:
- 库函数,不稳定 (因为早期Unix并没有线程的概念,线程概念是后加的,所以,是放在了库函数中)
- 调试、编写困难
- 对信号支持不好
四、线程控制函数
获取线程ID pthread_self
功能:获取线程ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void);
成功:返回线程ID 失败:无
线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)
注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。
创建线程pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
thread 线程的ID,传出参数
attr 代表线程的属性,通常传NULL,表示使用线程默认属性
第三个参数 函数指针, 函数形式void *func(void*)
arg 线程执行函数的参数
返回值 成功 0 失败 errno
编译时需要加 -lpthread
注意:线程ID在进程内是唯一的,但是在整个操作系统内部不一定是唯一的。
应用举例
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void *thr(void* arg){
//无符号长整型数 %lu
printf("I am a thread! pid=%d, tid=%lu\n", getpid(), pthread_self());
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
printf("I am main thread, pid=%d, tid=%lu\n", getpid(), pthread_self());
sleep(1);
return 0;
}
编译运行
运行结果:
上面的代码存在一个问题:
就是如果主线程不睡眠,则另一个线程没有机会去执行(因为主线程打印之后就直接执行return 0
了)。如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行.
将上面代码中的sleep(1)
换成如下语句即可:
pthread_exit(NULL);
线程退出函数pthread_exit
功能:将单个线程退出
void pthread_exit(void *retval);//参数:retval表示线程退出状态,通常传NULL
**参数:**retval表示线程退出状态,通常传NULL
注意:
线程中,禁止使用exit函数,会导致进程内所有线程全部退出。多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。
另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
线程回收pthread_join
功能:线程回收函数,阻塞等待线程退出,获取线程退出状态;其作用,对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval);
参数:
- threa——传出参数
- retval ——存储线程结束状态(注意是void **)
进程中:main返回值、exit参数–>int;等待子进程结束 wait 函数参数–>int *
线程中:线程主函数返回值、pthread_exit–>void *;等待线程结束 pthread_join 函数参数–>void **
**函数返回值:**成功:0;失败:错误号
参数 retval 非空用法:
- 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
举例:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thr(void *arg){
printf("I am a thread, tid=%lu\n", pthread_self());
sleep(5);
printf("I am a thread, tid=%lu\n", pthread_self());
return (void*)100;
}
int main(){
pthread_t tid;
pthread_create(&tid, NULL, thr, NULL);
void *ret; //注意void **retval,所以声明void *
//线程回收函数
pthread_join(tid, &ret);
printf("ret exit with %d\n",(int)ret);
pthread_exit(NULL);
return 0;
}
输出结果:
线程分离pthread_detach
功能:实现线程分离
int pthread_detach(pthread_t thread);//成功:0;失败:错误号
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
也可使用 pthread_create函数参2(线程属性)来设置线程分离。
注意:
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
举例:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void *thr(void *arg){
printf("I am a thread, self=%lu\n", pthread_self());
sleep(4