目录html
前情提要: Linux用户级线程和内核级线程区别linux
类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。所以在这类系统中,进程和线程关系密切。数组
轻量级进程(light-weight process),也有PCB,建立线程使用的底层函数和进程同样,都是clone多线程
从内核里看进程和线程是同样的,都有各自不一样的PCB,可是PCB中指向内存资源的三级页表是相同的并发
进程能够蜕变成线程函数
线程可看作寄存器和栈的集合线程
在linux下,线程最是小的执行单位;进程是最小的分配资源单位3d
查看线程命令:ps -elf|grep thread指针
三级映射:进程PCB --> 页目录(可当作数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元调试
线程共享资源 | 线程不共享资源 |
---|---|
文件描述符表 | 线程id |
每种信号的处理方式 | 处理器现场和栈指针(内核栈) |
当前工做目录 | 独立的栈空间(用户空间栈) |
用户ID和组ID | errno变量 |
内存地址空间(.text/.data/.bss/heap/共享库) | 信号屏蔽字 |
调度优先级 |
优势: 1. 提升程序并发性 2. 开销小 3. 数据通讯、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持很差
获取线程ID。其做用对应进程中 getpid() 函数。
pthread_t pthread_self(void); 返回值:成功:0; 失败:无!
线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其余系统中多是结构体实现
线程ID是进程内部,识别标志。(两个进程间,线程ID容许相同)
建立一个新线程。 其做用,对应进程中fork() 函数。
int pthread_create(pthread_t thread, const pthread_attr_t attr, void (start_routine) (void ), void arg);
返回值:成功:0; 失败:错误号 -----Linux环境下,全部线程特色,失败均直接返回错误号。
参数:
pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
参数1:传出参数,保存系统为咱们分配好的线程ID
参数2:一般传NULL,表示使用线程默认属性。若想使用具体属性也能够修改该参数。
参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数4:线程主函数执行期间所使用的参数。
练习:建立一个新线程,打印线程ID。注意:连接线程库 -lpthread
#include <stdio.h> #include <pthread.h> #include <unistd.h> void *tfn(void *arg) { printf("I'm thread, Thread_ID = %lu\n", pthread_self()); return NULL; } int main(void) { pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); printf("I am main, my pid = %d\n", getpid()); return 0; }
线程默认共享数据段、代码段等地址空间,经常使用的是全局变量,或者传参形式。而进程不共享全局变量,只能借助mmap。
全局变量:
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> int var = 100; void *tfn(void *arg) { var = 200; printf("thread\n"); return NULL; } int main(void) { printf("At first var = %d\n", var); pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); printf("after pthread_create, var = %d\n", var); return 0; }
传参:
#include <func.h> void *tfn(void *arg){ int* var = (int*)arg; *var = 200; printf("thread\n"); return NULL; } int main() { int var = 100; printf("At first var = %d\n", var); pthread_t tid; pthread_create(&tid, NULL, tfn, &var); sleep(1); printf("after pthread_create, var = %d\n", var); return 0; }
做用:将单个线程退出
void pthread_exit(void *retval); 参数:retval表示线程退出状态,一般传NULL
线程中,**禁止使用exit函数,会致使进程内全部线程所有退出**。因此,多线程环境中,应尽可能少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit致使进程退出,其余线程未工做结束,主控线程退出时不能return或exit。
另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,由于当其它线程获得这个返回指针时线程函数已经退出了。
阻塞等待线程退出,获取线程退出状态 其做用,对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
参数:thread:线程ID (【注意】:不是指针);retval:存储线程结束状态。
对比记忆:
进程中:main返回值、exit参数-->int;等待子进程结束 wait 函数参数-->int *
线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不一样的方法终止,经过pthread_join获得的终止状态是不一样的,总结以下:
若是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> #include <stdlib.h> typedef struct { int a; int b; } exit_t; void *tfn(void *arg) { exit_t *ret; ret = malloc(sizeof(exit_t)); ret->a = 100; ret->b = 300; pthread_exit((void *)ret); } int main(void) { pthread_t tid; exit_t *retval; pthread_create(&tid, NULL, tfn, NULL); /*调用pthread_join能够获取线程的退出状态*/ pthread_join(tid, (void **)&retval); //wait(&status); printf("a = %d, b = %d \n", retval->a, retval->b); return 0; }
杀死(取消)线程 其做用,对应进程中 kill() 函数。
int pthread_cancel(pthread_t thread); 成功:0;失败:错误号
【注意】:线程的取消并非实时的,而有必定的延时。须要等待线程到达某个取消点(检查点)。
相似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是马上就能完成,必需要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动做的一个位置。一般是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads能够查看具有这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,能够经过调用pthreestcancel函数自行设置一个取消点。
被取消的线程, 退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。所以当咱们对一个已经被取消的线程使用pthread_join回收时,获得的返回值为-1。
终止线程的三种方法。注意“取消点”的概念。
#include <stdio.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h> void *tfn1(void *arg) { printf("thread 1 returning\n"); return (void *)111; } void *tfn2(void *arg) { printf("thread 2 exiting\n"); pthread_exit((void *)222); } void *tfn3(void *arg) { while (1) { //printf("thread 3: I'm going to die in 3 seconds ...\n"); //sleep(1); pthread_testcancel(); //本身添加取消点*/ } return (void *)666; } int main(void) { pthread_t tid; void *tret = NULL; pthread_create(&tid, NULL, tfn1, NULL); pthread_join(tid, &tret); printf("thread 1 exit code = %d\n\n", (int)tret); pthread_create(&tid, NULL, tfn2, NULL); pthread_join(tid, &tret); printf("thread 2 exit code = %d\n\n", (int)tret); pthread_create(&tid, NULL, tfn3, NULL); sleep(3); pthread_cancel(tid); pthread_join(tid, &tret); printf("thread 3 exit code = %d\n", (int)tret); return 0; }
总结:终止某个线程而不终止整个进程,有三种方法:
从线程主函数return。这种方法对主控线程不适用,从main函数return至关于调用exit。
一个线程能够调用pthread_cancel终止同一进程中的另外一个线程。
线程能够调用pthread_exit终止本身。
进程 线程
fork pthread_create
exit pthread_exit
wait pthread_join
kill pthread_cancel
getpid pthread_self 命名空间