线程概述
与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。
一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同的程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。
(传统意义上的unix进程只是多线程的一个特例 该进程只包含一个线程)
进程是cpu分配资源的最小单位,线程是OS调度执行的最小单位。
线程是轻量级的进程LWP(light weight process)在linux环境下线程的本质仍是进程。
查看指定进程的LWP号 ps -Lf 指定进程的pid (PID都一样 但每个线程都有自己的LWP号)
为什么有了进程后还有有线程
进程间的信息难以共享。对于父子进程来说除去只读代码区外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
调用fork()来创建进程的代价相对较高,即使利用写时拷贝技术,仍需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲。
线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆malloc)变量中即可。
创建线程比创建进程通常要快10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时拷贝来复制内存也无需复制页表。
所有线程共用同一块虚拟地址空间,但是会给不同线程划分栈空间和.text代码段的空间(将栈空间和.text分成小块分给这些线程),main线程(主线程)的栈、线程1的栈… main线程的代码,线程1的代码…,其他部分都是共享相同的。
线程间共享的资源
进程PID 父进程ID PPID
进程组ID 会话ID
用户ID 用户组ID
文件描述符表
信号处置 (信号处理)
文件系统相关信息:文件权限掩码(mask)、当前工作目录
虚拟地址空间(除栈和.text段)
非共享资源
线程ID
信号掩码 (阻塞信号集)
线程特有数据
error变量
实时调度策略和优先级
栈、本地变量和函数的调用链接信息
查看当前pthread库(NPTL)版本 getconf GNU_LIBPTHREAD_VERSION
创建线程
man pthread_加tab可以查看基本上所有线程相关的函数
#include
int pthread_create(pthread_t thread, const pthread_attr_t attr,
void (start_routine) (void ), void arg);
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
void pthread_exit(void retval);
int pthread_join(pthread_t thread, void *retval);
int pthread_detach(pthread_t thread);
int pthread_cancel(pthread_t thread);
/*#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);创建一个子线程(main为主线程)thread 传出参数,当子线程创建成功后线程id会由这个指针传出attr 设置的线程属性 使用默认值 传入nullstart_routine 子线程所要处理的代码的函数的指针arg 为start_routine传参返回值 成功返回0 失败返回错误号(man3中查看) 这个错误号和之前errno不太一样 不能通过perror输出获取出错误号的信息 在string.h中char* strerror(int errnum)来获得这个错误号对应的错误信息注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthreadgcc learn_pthread_create.c -0 create -pthreadgcc learn_pthread_create.c -0 create -l pthread都对*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, NULL, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}for (int i = 0; i < 5; i++){printf("parent thread\n");}sleep(1); //防止主线程结束了 子线程的callback还没执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0;}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);return NULL;}
终止线程
/*#include <pthread.h>void pthread_exit(void *retval);终止一个线程 在哪个线程中调用 就是终止哪个线程retval 传出参数 可被pthread_join函数获取该线程的返回值pthread_t pthread_self(void)获取当前线程的线程IDint pthread_equal(pthread_t t1, pthread_t t2);判断t1与t2的线程号是否相等 为什么不用==判断呢因为不同OS pthread_t类型的实现不一样 有的是无符号长整型(当前Linux系统中是这种类型) 有的是结构体实现的 结构体就不能==了注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthreadgcc learn_pthread_create.c -0 create -pthreadgcc learn_pthread_create.c -0 create -l pthread都对*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, NULL, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}for (int i = 0; i < 5; i++){printf("parent thread\n");}pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程//主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsitprintf("main thread exsit\n"); //不会执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0; //不会执行// 主线程、子线程调用exit, pthread_exit,互相产生的影响。// 1、在主线程中,在main函数中return了或是调用了exit函数,则主线程退出,且整个进程也会终止,// 此时进程中的所有线程也将终止。因此要避免main函数过早结束。// 2、在主线程中调用pthread_exit, 则仅仅是主线程结束,进程不会结束,进程内的其他线程也不会结束,// 直到所有线程结束,进程才会终止。// 3、在任何一个线程中调用exit函数都会导致进程结束。进程一旦结束,那么进程中的所有线程都将结束。/*** 1: 线程使用return (这种方法对线程还适用,从main函数return 相当于调用exit但是在子线程中return 相当于调用pthread_exit 除了最后一个子线程return 那也相当于exit)* 2: 调用pthread_cancel (一个线程可以调用pthread_cancel终止同一进程中的另一个线程)* 3: 调用pthread_exit(线程可以调用pthread_exit终止自己,有两种情况需要注意:* 一种情况是,在主线程中,如果从main函数返回或是调用了exit函数退出主线程,* 则整个进程将终止,此时进程中有线程也将终止,因此在主线程中不能过早地从main* 函数返回;* 另外一种情况:如果主线程调用pthread_exit函数,则仅仅是主线程消亡,* 进程不会结束,进程内的其他子线程也不会终止,直到所有线程结束,进程才会结束;* 线程终止最重要的问题是资源释放问题,特别是一些临界资源在一段时间内只能被* 一个线程所持有,当线程要使用临界资源需提出请求,如果该资源未被使用则申请* 成功,否则等待。临界资源使用完毕后要释放以便其它线程可以使用。**/}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);pthread_t id = pthread_self();printf("id: %ld\n", id);return NULL; //相当于pthread_exit(NULL)}
连接已终止的线程
/*#include <pthread.h>int pthread_join(pthread_t thread, void **retval);连接已经终止的线程 回收子线程的资源 此函数阻塞没有线程结束这个函数一直阻塞 直到有线程结束此函数回收后才能运行下面的语句调用一次只能回收一个线程 一般在主线程中使用参数 thread需要回收的线程ID retval 接收子线程退出时的返回值 二级指针线程结束时(通过pthread_exit退出传入返回值)这个返回值可被retval得到返回值 成功返回0 失败返回错误号(man3中查看) 这个错误号和之前errno不太一样 不能通过perror输出获取出错误号的信息 在string.h中char* strerror(int errnum)来获得这个错误号对应的错误信息类比进程,子进程结束父进程有责任wait or waitpid去回收子进程的PCB资源 如果不回收子进程将变为僵尸进程子线程结束后不会收将会产生僵尸线程子线程不需要一定要主线程(main线程)去回收 任何线程都可以去回收其他已经结束的线程注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthreadgcc learn_pthread_create.c -0 create -pthreadgcc learn_pthread_create.c -0 create -l pthread都对*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, NULL, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}for (int i = 0; i < 5; i++){printf("parent thread\n");}int *t_return;//返回值是二级指针 是因为callback的返回值类型被规定为一级指针void*pthread_join(tid, (void **)&t_return); //阻塞回收子线程资源//子线程的返回值保存在 t_return中printf("son thread return %d\n", *t_return);pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程//主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsitprintf("main thread exsit\n"); //不会执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0; //不会执行}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);pthread_t id = pthread_self();printf("id: %ld\n", id);// int val = 100; //局部变量 返回的还是这个变量的地址//主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值static int val = 100; //全局存储区//通过pthread_exit结束子进程 并返回值pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的// return NULL; //相当于pthread_exit(NULL)}
线程的分离
/*#include <pthread.h>int pthread_detach(pthread_t thread);标记传入的线程thread为detach分离状态当分离状态的线程终止 其资源会自动释放还给系统 不再需要其他线程join来回收此线程的资源!!!!当去分离一个已经是分离状态的线程将会导致位未知的结果 不能去连接(join)一个已经分离(detach)的线程成功返回0 失败返回strerror的错误号注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthreadgcc learn_pthread_create.c -0 create -pthreadgcc learn_pthread_create.c -0 create -l pthread都对*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, NULL, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}for (int i = 0; i < 5; i++){printf("parent thread\n");}pthread_detach(tid); //设置子线程分离 当分离状态的线程终止 其资源会自动释放还给系统 不再需要其他线程join来回收此线程的资源!!!!pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程//主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsitprintf("main thread exsit\n"); //不会执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0; //不会执行}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);pthread_t id = pthread_self();printf("id: %ld\n", id);// int val = 100; //局部变量 返回的还是这个变量的地址//主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值static int val = 100; //全局存储区//通过pthread_exit结束子进程 并返回值pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的// return NULL; //相当于pthread_exit(NULL)}
线程取消
/*#include <pthread.h>int pthread_cancel(pthread_t thread);给传入的thread号线程发送取消请求(终止线程)对应线程是否以及合适终止 取决于对应线程的两个参数 他的cancelabilityd的state以及type在pthread_create创建线程的第二个参数可以传入一个结构体来设置这两个属性系统并不能保证在调用pthread_cancel后立即取消线程只有当线程运行到cancellation point取消点函数 线程会才会取消!!!!!!!取消点函数可以在man pthreads中查看有很多 一般是系统调用 所以我们可以粗略地人为一般的系统调用是取消点函数如果子线程中没有取消点的话,子线程会一直运行到结束(return or pthread_exit)或者可以通过 pthread_setcanceltype成功返回0 失败返回strerror的错误号注意线程pthread库并非linux gcc自带库 libpthread.so 在编译时需要指定-pthreadgcc learn_pthread_create.c -0 create -pthreadgcc learn_pthread_create.c -0 create -l pthread都对*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, NULL, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}//取消子线程pthread_cancel(tid); //子线程中的printf是取消点for (int i = 0; i < 5; i++){printf("parent thread\n");}pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程//主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsitprintf("main thread exsit\n"); //不会执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0; //不会执行}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);pthread_t id = pthread_self();printf("id: %ld\n", id);// int val = 100; //局部变量 返回的还是这个变量的地址//主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值static int val = 100; //全局存储区//通过pthread_exit结束子进程 并返回值pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的// return NULL; //相当于pthread_exit(NULL)}
线程属性
pthreadcreate的第二个参数pthread_attr_t* attr 设置线程属性
pthread_attr_t为线程属性类型
man pthread_attr 加两次tab可以看到所有attr相关函数
int pthread_attr_init(pthread_attr_t attr);
int pthread_attr_destroy(pthread_attr_t attr);
int pthread_attr_getdetachstate(const pthread_attr_t attr,int detachstate);
int pthread_attr_setdetachstate(pthread_attr_t attr,int detachstate);
线程栈的相关设置函数
pthread_attr_setstack pthread_attr_setstackaddr pthread_attr_setstacksize
/*int pthread_attr_init(pthread_attr_t* attr);初始化线程属性变量线程属性不止一些标志位还有一些复杂的变量 所以在pthread_attr_t创建出来后不能简单地 显式地设置一些属性 需要调用init函数来将这个变量清空int pthread_attr_destroy(pthread_attr_t* attr);释放线程属性的资源int pthread_attr_getdetachstate(const pthread_attr_t* attr,int *detachstate);获取线程分离状态的属性detachstate为传出参数创建线程时可以直接指定线程是detached状态还是joinable状态(其实就是不分离的状态)PTHREAD_CREATE_DETACHED 线程结束后不需要其他线程去join回收资源 系统会回收这个线程的资源PTHREAD_CREATE_JOINABLE(默认的线程的状态)int pthread_attr_setdetachstate(pthread_attr_t* attr,int *detachstate);设置线程分离状态的属性int pthread_attr_getstacksize(const pthread_attr_t* attr,size_t *stacksize);stacksize为传出参数是线程栈的大小*/#include <stdio.h>#include <pthread.h>#include <string.h>#include <unistd.h>void *callback(void *arg);int main(){//创建线程属性变量pthread_attr_t attr;//线程属性不止一些标志位还有一些复杂的变量 所以在pthread_attr_t创建出来后//不能简单地 显式地设置一些属性 需要调用init函数来将这个变量清空pthread_attr_init(&attr);//设置线程创建后的分离状态的属性pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //使用attr创建后就是分离状态//获取线程栈的大小(因为我们没有设置过线程栈的大小 所以这里获取到的是线程栈的默认大小)size_t *size;pthread_attr_getstacksize(&attr, &size);printf("thread default stacksize is %ld\n", size); //8388608B = 8MB 默认线程栈大小为8MB// 创建一个子线程pthread_t tid;int num = 10; //可以搞个结构体将函数的参数全部放在那个结构体里 再往回调函数内传int ret = pthread_create(&tid, &attr, callback, (void *)&num);if (ret != 0){printf("%s\n", strerror(ret));}for (int i = 0; i < 5; i++){printf("parent thread\n");}//释放子线程属性资源pthread_attr_destroy(&attr);pthread_exit(NULL); //让子线程退出 当主线程退出时 不会影响其他正常运行的线程//主线程结束 pthread_exit下面的代码是不会执行的 即不会打印main thread exsitprintf("main thread exsit\n"); //不会执行//当主线程return 相当于进程exit所有子线程全部结束!!!return 0; //不会执行}void *callback(void *arg){printf("child thread\n");printf("arg value %d\n", *(int *)arg);pthread_t id = pthread_self();printf("id: %ld\n", id);// int val = 100; //局部变量 返回的还是这个变量的地址//主线程拿去的时候这个变量已经销毁了(子线程栈释放了) 主线程得到的是野内存随机值static int val = 100; //全局存储区//通过pthread_exit结束子进程 并返回值pthread_exit((void *)&val); //与return (void *)&val是一样的!!!!!! 返回一个局部变量是没有意义的// return NULL; //相当于pthread_exit(NULL)}
