一、线程
线程的概念:
- 在一个程序里的执行路线就叫做线程,或者说线程是一个进程内部的执行流
- 一个进程至少有一个执行流,也就是主线程
- 线程和线程之间大部分资源都是共享的
Linux下的进程与线程
- 图解:
线程和进程对比来看:
- 线程在进程内部执行
- 线程比进程执行流更小,执行粒度更小
- 线程的切换比进程切换成本小
- 线程的创建比进程轻量化很多,所以创建和释放成本都非常低,而且Linux下是没有具体的线程的,Linux内核同样用PCB来描述线程,只不过同组线程具有相同的pid(进程ID),所以Linux下的线程被看做是轻量化进程
- 线程出错也相当于进程出错,也就是某个线程因为中断,异常等挂掉,该进程也会挂掉
进程内线程共享的资源:
- 同一地址空间,代码段和数据段都是共享的,在一个程序内定义一个函数或者全局变量,该进程内的所有线程都可以调用该函数,访问该变量
- 文件描述符表
- 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户ID和组ID
各线程独享的资源
- 私有栈:一个线程调用一个函数时,该函数内部的局部变量等都在该进程私有的栈空间内,其他线程无权访问
- 独立上下文:线程被切换时,它的上下文数据都是自己独有的,等到被切换回来时上下文数据也会恢复,继续执行之前的程序
- 信号屏蔽字
- 线程ID(不同于进程ID,每个线程都有自己独有的标识符)等
线程控制
POSIX线程库
- 绝大多数与线程相关的函数都是以”pthread_”开头的
- 使用这些函数时,需要引入头文件”pthread.h”
- 链接这些线程函数库时要使用编译器命令的”-lpthread”选项
创建线程
pthread_create函数
函数体:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
thread:返回线程ID
attr:设置线程的属性,attr为NULL时表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查:
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误
- pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
- pthreads同样也提供了线程内的errno变量,因为读取返回值要比读取线程内的errno变量的开销更小
线程终止
如果是只终止某个线程,而不终止整个进程,则有下面的三种方法
- 线程调用的函数通过return返回,但对主线程不适用,从main函数调用return则相当于整个进程终止
- 线程调用pthread_exit函数终止自己,注意线程调用的函数调用exit时,会使整个进程终止
- 一个线程通过调用pthread_cancelt函数终结别的进程
pthread_exit函数
函数体:
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一个局部变量,因为当其他线程得到这个返回指针时线程函数已经退出了
返回值:无返回值,和进程一样,线程结束的时候无法返回到它的调用者(自身)
pthread_cancel函数
函数体:
int pthread_cancel(pthread_t thread);
thread:线程ID
返回值:成功返回0,失败返回错误码
线程等待
一个线程退出后,其空间并不会自动释放,必须有另外一个线程来回收其资源,并且创建新的线程不会占用刚退出的线程的地址空间,
pthread_join函数
函数体:
int pthread_join(pthread_t thread, void **value_ptr);
thread:线程ID
value_ptr:它指向一个指针,后者返回线程的返回值
返回值:成功返回0,失败返回错误码
线程分离
线程在调用join方法时,进行的是阻塞式的等待,所以当我们不关心线程的返回值时,join方法是一种负担,我们就可以通过调用线程分离函数来使线程退出时,自动释放线程资源
需要额外注意的是,即使线程被分离,可线程出现异常时,仍然会影响进程,因为线程始终是在进程内执行的
在线程已经分离后,再有别的进程去调用join方法去等待其退出时,join方法会返回错误码,因为一个分离的线程是不能被其他线程回收或杀死的
pthread_detach函数
函数体:
int pthread_detach(pthread_t thread);
线程组内其他线程对目标线程进行分离
pthread_detach(pthread_self());
线程自己分离
线程ID与进程ID
- 一个程序下的所有线程同属于一个组,简称线程组,即多线程的进程,这些线程因为同属于一个进程,所以拥有相同的进程ID(PID),而每个线程又拥有各自的进程描述符(因为Linux下,线程被看做是轻量级进程),即线程ID
void *thread_run(void *arg)
{
const char* msg = (const char*)arg;
while(1){//通过死循环控制不让线程终止
printf("I am %s ,my id is %d\n",msg,syscall(SYS_gettid));
}
}
int main()
{
pthread_t tid1,tid2,tid3,tid4;
//创建四个线程,该进程下就会拥有五个线程
pthread_create(&tid1,NULL,thread_run,"thread1");
pthread_create(&tid2,NULL,thread_run,"thread2");
pthread_create(&tid3,NULL,thread_run,"thread3");
pthread_create(&tid4,NULL,thread_run,"thread4");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
}
查看当前的线程状况
- LWP(Light Weighted Process)线程ID,同组的线程的进程ID是相同的,这两个概念不要搞混了
- NLWP:线程组内线程的个数
线程ID不同于pthread_t类型的ID,线程ID是pid_t类型的变量,而且是用来唯一标识线程的一个整形变量,pthread_t类型的ID只是一个地址
为什么有两个thread ID:
线程库实际上由两部分组成:内核的线程支持+用户态的库支持(glibc)。Linux在早期内核不支持线程的时候,glibc就在库中(用户态)以线程(就是用户态线程)的方式支持多线程了。POSIX thread只对用户编程的调用接口作了要求,而对内核接口没有要求。linux上的线程实现就是在内核支持的基础上,以POSIX thread的方式对外封装了接口,所以才会有两个ID的问题。