学习视频链接
黑马程序员-Linux系统编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1KE411q7ee?p=151&spm_id_from=333.1007.top_right_bar_window_history.content.click
目录
一、线程概念
1.1 什么是线程
1.2 查看火狐浏览器的线程
1.3 Linux内核线程实现原理
1.4 线程共享资源
1.5 线程非共享资源
1.6 线程优、缺点
二、进程控制原语
2.1 pthread_self 函数
2.2 pthread_create 函数
2.3 测试代码
一、线程概念
1.1 什么是线程
LWP:light weight process 轻量级的进程,本质仍是进程(在 Linux 环境下)
进程:独立地址空间,拥有 PCB
线程:有独立的 PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间 独居(进程);合租(线程)
Linux下:
线程:最小的执行单位
进程:最小分配资源单位,可看成是只有一个线程的进程
进程创建线程,每个线程都有 PCB,原来的进程变成线程了
1.2 查看火狐浏览器的线程
进程 id 是同一个,进程号有多少,共有 22 个
1.3 Linux内核线程实现原理
类 Unix 系统中,早期是没有“线程”概念的,80 年代才引入(应减少信号和线程混用),借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切
1、轻量级进程(light-weight process),也有 PCB,创建线程使用的底层函数和进程一样,都是 clone
2、从内核里看进程和线程是一样的,都有各自不同的 PCB,但是 PCB 中指向内存资源的三级页表(参考操作系统分页式存储)是相同的
3、进程可以蜕变成线程
4、线程可看做寄存器和栈的集合
5、在 linux 下,线程最是小的执行单位;进程是最小的分配资源单位
察看 LWP 号:ps -Lf pid 查看指定线程的 lwp 号
1.4 线程共享资源
1、文件描述符表
2、每种信号的处理方式
3、当前工作目录
4、用户 ID 和组 ID
5、内存地址空间(.text/ .data/ .bss/heap/共享库)(不贡献栈)
1.5 线程非共享资源
1、线程 ID
2、处理器现场和栈指针(内核栈)
3、独立的栈空间(用户空间栈)
4、errno 变量
5、信号屏蔽字
6、调度优先级
1.6 线程优、缺点
1、优点
提高程序并发性,开销小,数据通信、共享数据方便
2、缺点
库函数、不稳定,调试、编写困难、GDB不支持,对信号支持不好
优点相对凸出,缺点不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大
二、进程控制原语
2.1 pthread_self 函数
1、作用
获取线程 ID。其作用对应进程中 getpid() 函数
2、pthread_t pthread self(void);
返回值:成功:0;失败:无
3、线程 ID
pthread_t 类型,本质:在Linux下为无符号整数 (%lu),其他系统中可能是结构体实现
线程 ID 是进程内部 识别标志。(两个进程间,线程 ID 允许相同)
4、注意
不应使用全局变量 pthread_t tid; 在子线程中通过 pthread_create 传出参数来获取线程 ID,而使用 pthread_self
2.2 pthread_create 函数
1、作用
创建一个新线程 其作用,对应进程中 fork() 函数
2、int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值:成功:0; 失败:错误号 —— Linux 环境下,所有线程特点,失败均直接返回错误号
参数:
参数1:传出参数,保存系统为我们分配好的线程 ID
参数2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束
参数4:线程主函数执行期间所使用的参数
2.3 测试代码
1、查看父进程的线程 id
2、创建线程,主线程执行 main 函数,创建的线程执行 tfn
3、循环创建多个子线程
4、错误写法
#include#include #include #include #include #include void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { int i = *((int *)arg); sleep(i); printf("I'm %dth thread: pid = %d, tid = %lun", i + 1, getpid(), pthread_self()); return NULL; } int main(int argc, char *argv[]) { int i; int ret; pthread_t tid; for (int i = 0; i < 5; i++) { ret = pthread_create(&tid, NULL, tfn, (void*)&i); if (ret != 0) { perror("pthread_create error"); } } sleep(5); printf("I'm main thread: pid = %d, tid = %lun", getpid(), pthread_self()); return 0; }
原因:地址传递和值传递的效果不一样,线程传参要用值传递而不是地址传递。其中要借助两次强制类型转换 int(4字节) —> char(8字节) —> int(4字节)
5、线程间全局变量共享
#include2.4 pthread_exit#include #include #include #include #include int var = 100; void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { var = 200; printf("In thread, change var = %dn", var); return NULL; } int main(void) { printf("At first var = %dn", var); pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); printf("After pthread_create var = %dn", var); return 0; }
1、作用
将单个线程退出
2、以前代码存在的问题
(1) 我们想退出某个线程,使用 exit 会退出整个进程
#include#include #include #include #include #include void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { int i =(int)arg; sleep(i); if (i == 2) { exit(0); } printf("I'm %dth thread: pid = %d, tid = %lun", i + 1, getpid(), pthread_self()); return NULL; } int main(void) { int i; int ret; pthread_t tid; for (i = 0; i < 5; i++) { ret = pthread_create(&tid, NULL, tfn, (void *)i); if (ret != 0) { sys_err("pthread_create error"); } } sleep(5); return 0; }
(2) return 能达到退出线程的目的
但是在函数调用内使用 return 是达不到这样的效果的
3、void pthread_exit(void *retval);
参数:retval 表示线程退出状态,通常传 NULL
在不添加 sleep 控制输出顺序的情况下。pthread_create 在循环中,几乎瞬间创建 5 个线程,但只有第 1 个线程有机会输出(或者第 2 个也有,也可能没有,取决于内核调度)如果第 3 个线程执行了 exit,将整个进程退出了,所以全部线程退出了
所以,多线程环境中,应尽量少用,或者不使用 exit 函数,取而代之使用 pthread_exit 函数,将单个线程退出。任何线程里 exit 导致进程退出,其他线程未工作结束,主控线程退出时不能 return 或 exit
另注意,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了
4、代码
现在主线程不用睡眠了,只退出主线程就可以了,子线程不受影响
5、总结
在主函数里面执行 return 和 exit 效果是一样的
return:回到调用者那里去
exit:退出当前进程
pthread_exit():退出当前进程
2.5 pthread_join函数1、作用
阻塞等待线程退出,获取线程退出状态其作用, 对应进程中 waitpid() 函数
2、int pthread_join(pthread_t thread, void **retval);
成功:0;失败:错误号
参数:thread:线程 ID([注意]:不是指针);retval:存储线程结束状态
和 wait 类比,wait 退出值是 int 所以传出参数是 int *,线程返回值是 void * 所以传出参数是 void **
3、代码
#include#include #include #include #include #include struct thrd { int var; char str[256]; }; void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { struct thrd *tval; tval = malloc(sizeof(tval)); tval->var = 100; strcpy(tval->str, "hello thread"); return (void*)tval; } int main(void) { pthread_t tid; struct thrd *retval; int ret = pthread_create(&tid, NULL, tfn, NULL); if (ret != 0) { sys_err("pthread_create error"); } ret = pthread_join(tid, (void **)&retval); if (ret != 0) { sys_err("phread_join error"); } printf("child thread exit with var = %d, str = %sn", retval->var, retval->str); pthread_exit(NULL); // 将当前线程退出 }
2.6 pthread_cancel函数
1、作用
杀死(取消)线程。 其作用,对应进程中 kil() 函数
2、int pthread_cancel(pthread_t thread);
成功:0;失败:错误号
[注意] 线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)
类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat,open,pause,close,read,write .... 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节
可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调pthread_testcancel 函数自行设置一个取消点
被取消的线程,退出值定义在 Linux 的 pthread 库中。常数 PTHREAD_CANCELED 的值是 -1 可在头文件 pthread.h 中找到它的定义:#define PTHREAD_CANCELED(void *) -1)。因此当我们对一个已经被取消的线程使用 pthread_join 回收时,得到的返回值为 -1
3、代码
(1) 杀死子进程演示
#include#include #include #include #include #include void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { while (1) { printf("thread: pid = %d, tid = %lun", getpid(), pthread_self()); sleep(1); } return NULL; } int main(void) { pthread_t tid; int ret = pthread_create(&tid, NULL, tfn, NULL); if (ret != 0) { sys_err("pthread_create error"); } printf("main: pid = %d, tid = %lun", getpid(), pthread_self()); sleep(5); ret = pthread_cancel(tid); if (ret != 0) { sys_err("phread_join error"); } while(1); pthread_exit(NULL); }
(2) 测试被杀死的子进程的返回值
#include#include #include #include #include #include void sys_err(const char *str) { perror(str); exit(1); } void *tfn1(void *arg) { printf("thread 1 returningn"); return (void *)111; } void *tfn2(void *arg) { printf("thread 2 exitingn"); pthread_exit((void *)222); } void *tfn3(void *arg) { while(1) { printf("thread 3 : I'm going to die in 3 seconds ...n"); sleep(1); } return (void *)111; } int main(void) { pthread_t tid; void *tret = NULL; pthread_create(&tid, NULL, tfn1, NULL); pthread_join(tid, &tret); printf("thread 1 exit code = %dnn", (int)tret); pthread_create(&tid, NULL, tfn2, NULL); pthread_join(tid, &tret); printf("thread 2 exit code = %dnn", (int)tret); pthread_create(&tid, NULL, tfn3, NULL); sleep(3); pthread_cancel(tid); pthread_join(tid, &tret); printf("thread 3 exit code = %dn", (int)tret); return 0; }
(3) 杀死一个进程需要一个契机(进入内核)
现在处于死循环进入不了内核
需要有系统调用或者手动添加一个取消点



