线程基础
1.进程->
传统操作系统上,进程就是pcb,操作系统通过pcb描述进程
Linux下,进程称为线程组(tgid--thread group id),线程组---抽象化的一个概念,同一个虚拟地址空间上的线程-pcb,具有相同的特征
2.线程->运行程序中的一个独立的执行流
操作系统运行调度的最小单元,包含在进程中。
Linux下,线程使用pcb模拟实现,每一个独立的pcb都是一个线程,称轻量级进程
查看线程信息:
ps -efL | grep create
eg: ps -efL | head -n 1 && ps -efL | grep create // 查看线程 id
PID(线程组id-tgid) PPID(父进程id) LWP(轻量级进程--tid)
---pid:LWP:轻量级进程id 进程id,使用 getpid() 获取
tid:地址,线程空间首地址 线程id,使用 pthread_t pthread_self(); 获取
-
进程和线程的区别和联系
-
进程是资源分配的基本单位,线程是 cpu 调度的基本单位
-
同一进程中的多条线程共用一个虚拟地址空间
-
一个进程-pid 等价于一个线程组-tgid
-
一个进程具有一个或多个线程,每一个线程都有自己的 pcb 和 tid
-
所有的线程同处于一个线程组,共享同一个 tgid
-
线程间的独有与共享(*****重要)
独有:栈,寄存器(保存上下文数据),信号屏蔽字(线程可以独立处理自己的信号),errno,线程标识符
共享:代码段(虚拟地址空间),数据段(mm_struct),文件描述符表(files_struct),信号处理方式(sighan_struct),当前工作路径,用户ID,用户组ID
-
线程与进程的优缺点
线程:线程间共享同一个虚拟地址空间
优点:线程间通信更方便,线程创建/销毁成本更低,切换调度成本更低,执行粒度更加细致
缺点:缺乏访问控制,编码难度高,健壮性较低,不安全
使用场景:对于一些对主程序的安全性和稳定性较高的程序使用多线程进行处理多任务---性能更高,比如 bash
进程:每一个进程都具有独立的虚拟地址空间
优点:进程间具有独立性,操作互不影响,编码简单
缺点:通信不方便,进程的创建/销毁成本高,切换调度成本高
使用场景:适用于程序间出现异常互不影响的服务器程序,某个子进程出现问题,其父进程不受影响
-
线程异常
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程。进程终止,该进程内的所有线程也就随即退出
线程控制 ---理解:线程、用户态线程、轻量级进程、进程
操作系统没有提供创建线程的系统调用接口,封装了一套线程的动态接口库--POSIX线程库 实现对线程的控制。
实质:使用库函数创建的线程其实就是一个用户态的线程,在内核中对应一个轻量级进程,通过调度这个用户态线程来实现对内核中轻量级进程的控制、每个线程都有自己的一份独立空间,并且这块空间在进程的虚拟地址空间中
【libpthread.so】动态库 【libpthread.a】静态库
注意事项:!!!【#include <pthread.h>,链接时加入参数" -lpthread "】
// 线程创建
int ptrhead_create(pthread_t *thread, const pthread_attr_t * attr, void*(*start_routine)(void*), void* arg);
thread “输出型参数--线程ID, 地址” ---使用 pthread_t pthread_self(); 获取调用线程空间首地址
attr “线程属性,线程栈大小、溢出缓冲区大小、线程特性的继承...通常置 NULL”
start_routine “线程入口函数”
arg “线程入口函数的参数”
返回值:0 失败:!=0 ---errno EAGAIN(当前设置不了)/EINVAL(无效设置属性)/EPERM(权限错误)
线程终止的三种方式:
- 从线程函数中 return,不适用与主线程:主线程调用 return 相当于调用 exit();
- 线程调用 pthread_exit 终止自己
- 调用 pthread_cancel 终止同一进程中的其它线程
void ptrhead_exit(void *value_ptr);
value_ptr “不要指向一个局部变量”
返回值:空
int pthread_cancel(pthread_t tid);
tid “线程 id”
返回值:0 失败:!= 0 ---errno
需要注意, pthread_exit(); 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
// 线程等待--等待指定线程的退出,获取线程的退出返回值,允许操作系统回收资源
// 默认情况下,新创建的线程是joinable的,处于 joinable 属性的线程才能被等待,也必须被等待,因为这种线程退出后,资源不会自动回收,从而造成内存泄露
int ptrhead_join(pthread_t tid, void **value_ptr);
tid “线程 id”
value_ptr “输出型参数,指向一个指针,这个指针指向线程的返回值”
返回值:0 失败:!= 0 ---errno
调用该函数的线程将挂起等待,直到id为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 属性设置成 detach,处于 detach 属性的线程,退出后资源自动回收
int ptrhead_detach(pthread_t tid);
tid “线程 id”
返回值:0 失败: != 0 ---errno
注意:必须让线程先分离
joinable 和 detach 是冲突的,一个线程不能既是 joinable 又是 detach 的
pthread_detach 分为新线程的分离和主线程的分离
新线程的分离: 新线程分离后,主线程可能不知道新线程的分离,因此主线程可能一直会去join等待
主线程的分离: 主线程确认与新线程分离,将不再等待新线程。