网站建设补充报价单,宝塔 wordpress 教程,安徽黄山网站建设,茂名网站制作计划线程 文章目录线程1 线程概念2 NPT安装线程 man page#xff1a;查看指定线程的 LWP 号#xff1a;3 线程的特点4 线程共享资源5 线程非共享资源6 线程的优缺点7线程常用操作1 线程号pthread_self函数#xff1a;pthread_equal函数:参考代码2 错误返回值分析参考代码3 线程的…线程 文章目录线程1 线程概念2 NPT安装线程 man page查看指定线程的 LWP 号3 线程的特点4 线程共享资源5 线程非共享资源6 线程的优缺点7线程常用操作1 线程号pthread_self函数pthread_equal函数:参考代码2 错误返回值分析参考代码3 线程的创建pthread_create函数参考代码创建多个线程传地址作为函数参数传值作为函数参数4 线程共享资源的验证共享数据段共享堆空间5 线程资源回收pthread_join函数参考代码6 线程分离pthread_detach函数参考代码7 线程退出pthread_exit函数参考代码8 线程取消pthread_calcel函数参考代码无效的线程取消参考代码线程对Cancel信号的处理参考代码(修复无效的线程取消)9 线程清理使用方法线程清理函数调用线程清理例程8 线程属性8.2 线程属性初始化和销毁8.3 线程分离状态8.4 线程栈地址8.5 线程栈大小8.6 综合参考程序8.7 线程使用注意事项补充 : pthread_join的第二个参数1 线程概念 在许多经典的操作系统教科书中总是把进程定义为程序的执行实例它并不执行什么, 只是维护应用程序所需的各种资源而线程则是真正的执行实体。所以线程是轻量级的进程LWPlight weight process在Linux环境下线程的本质仍是进程。为了让进程完成一定的工作进程必须至少包含一个线程。 进程直观点说保存在硬盘上的程序运行以后会在内存空间里形成一个独立的内存体这个内存体有自己的地址空间有自己的堆上级挂靠单位是操作系统。操作系统会以进程为单位分配系统资源所以我们也说进程是CPU分配资源的最小单位。 线程存在与进程当中(进程可以认为是线程的容器)是操作系统调度执行的最小单位。说通俗点线程就是干活的。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体是 CPU 调度和分派的基本单位它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源只拥有一点在运行中必不可少的资源如程序计数器一组寄存器和栈但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 如果说进程是一个资源管家负责从主人那里要资源的话那么线程就是干活的苦力。一个管家必须完成一项工作就需要最少一个苦力也就是说一个进程最少包含一个线程也可以包含多个线程。苦力要干活就需要依托于管家所以说一个线程必须属于某一个进程。 进程有自己的地址空间线程使用进程的地址空间也就是说进程里的资源线程都是有权访问的比如说堆啊栈啊静态存储区什么的。 多线程和多进程的区别 多进程共享的资源 代码文件描述符内存映射区 --mmap 多线程共享的资源 堆全局变量相比多进程更加节省系统资源对于系统 CPU 轮转时间片来说不论是线程还是进程它不认识只认 PCB。 主线程和子线程 共享 用户区内除了栈区是不共享的其余都是不共享的。 不共享 栈区当有 1 主 4 子线程时候栈区会被平分为 5 份 在 Linux 下 线程就是进程 – 轻量级的进程对于内核来说线程就是进程内核只会用 进程是操作系统分配资源的最小单位 线程是操作系统调度的最小单位 2 NPT 当 Linux 最初开发时在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程calling process的一个拷贝这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是这种方法有一些缺点尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外这个线程模型也不符合POSIX的要求。 要改进 LinuxThreads非常明显我们需要内核的支持并且需要重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPTNext-GenerationPOSIX Threads项目。同时Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了把这个领域完全留给了 NPTL。 NPTL或称为 Native POSIX Thread Library是 Linux 线程的一个新实现它克服了 LinuxThreads 的缺点同时也符合POSIX的需求。与 LinuxThreads 相比它在性能和稳定性方面都提供了重大的改进。 查看当前pthread库版本getconf GNU_LIBPTHREAD_VERSION 安装线程 man page 安装线程 man page 命令 sudo apt install manpages-posix-dev
1查看指定线程的 LWP 号
线程号和线程 ID 是有区别的线程号是给内核看的查看方式 找到程序的进程 IDps -Lf pid
一个例子查看火狐浏览器程序由多线程构成
如 Linux 下 查看火狐浏览器发现其是由多线程构成的
ps ajx | grep firefoxps -Lf 31102
1233 线程的特点
类Unix系统中早期是没有“线程”概念的80年代才引入借助进程机制实现出了线程的概念。因此在这类系统中进程和线程关系密切
\1) 线程是轻量级进程(light-weight process)也有PCB创建线程使用的底层函数和进程一样都是clone\2) 从内核里看进程和线程是一样的都有各自不同的PCB.\3) 进程可以蜕变成线程\4) 在linux下线程最是小的执行单位进程是最小的分配资源单位 查看指定进程的LWP号 ps -Lf pid 实际上无论是创建进程的fork还是创建线程的pthread_create底层实现都是调用同一个内核函数 clone 。
Ø 如果复制对方的地址空间那么就产出一个“进程”
Ø 如果共享对方的地址空间就产生一个“线程”。
Linux内核是不区分进程和线程的, 只在用户层面上进行区分。所以线程所有操作函数 pthread_* 是库函数而非系统调用。
4 线程共享资源 \1) 文件描述符表 \2) 每种信号的处理方式 \3) 当前工作目录 \4) 用户ID和组ID 内存地址空间 (.text/.data/.bss/heap/共享库)
5 线程非共享资源
\1) 线程id\2) 处理器现场和栈指针(内核栈)\3) 独立的栈空间(用户空间栈)\4) errno变量\5) 信号屏蔽字\6) 调度优先级
6 线程的优缺点
优点
Ø 提高程序并发性Ø 开销小Ø 数据通信、共享数据方便
缺点
Ø 库函数不稳定Ø 调试、编写困难、gdb不支持Ø 对信号支持不好
优点相对突出缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
7线程常用操作
1 线程号 就像每个进程都有一个进程号一样每个线程也有一个线程号。进程号在整个系统中是唯一的但线程号不同线程号只在它所属的进程环境中有效。 进程号用 pid_t 数据类型表示是一个非负整数。线程号则用 pthread_t 数据类型来表示Linux 使用无符号长整数表示。 有的系统在实现pthread_t 的时候用一个结构体来表示所以在可移植的操作系统实现不能把它做为整数处理。 pthread_self函数
#include pthread.h
pthread_t pthread_self(void);
功能获取线程号。
参数无
返回值调用线程的线程 ID 。
pthread_equal函数:
int pthread_equal(pthread_t t1, pthread_t t2);
功能判断线程号 t1 和 t2 是否相等。为了方便移植尽量使用函数来比较线程 ID。
参数t1t2待判断的线程号。
返回值相等 非 0不相等0参考代码
// todo 创建线程号,获取线程号,比较线程号
#include ../tou.h
int main()
{pthread_t tid 0; // todo 创建线程号//todo 如果不确定 pthread_t 是无符号的整型,还是一个结构体,可以使用下面的方式进行初始化
/*
memset(tid,0,sizeof(tid));
或者bzero(tid,sizeof(tid));
*/tid pthread_self(); // todo 获取当前线程的线程号printf(tid: %ld, tid);// todo 为了方便移植, 使用函数来比较线程idpthread_t tid2 pthread_self();if (pthread_equal(tid, tid2)){printf(两个线程相同\n);}else{printf(两个线程不同\n);}
}【注意】线程函数的程序在 pthread 库中故链接时要加上参数 -lpthread。 2 错误返回值分析 注意所有线程的错误号返回都只能使用strerror这个函数判断不能使用perror .因为perror是调用进程的全局错误号不适合单独线程的错误分析所以只能使用strerror。 参考代码
#include ../tou.hvoid *thrd_func(void *arg)
{printf(i am detach.\n);
}int main(void)
{pthread_t tid;int ret;ret pthread_create(tid, NULL, thrd_func, NULL);if (ret ! 0){fprintf(stderr, pthread_create error:%s\n, strerror(ret));exit(1);}ret pthread_detach(tid);if (ret ! 0){fprintf(stderr, pthread_detach error:%s\n, strerror(ret));exit(1);}sleep(1);ret pthread_join(tid, NULL);if (ret ! 0){fprintf(stderr, pthread_detach error:%s\n, strerror(ret));exit(1);}// 如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
}在一个线程中调用pthread_create()创建新的线程后当前线程从pthread_create()返回继续往下执行而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。 由于pthread_create的错误码不保存在errno中因此不能直接用perror()打印错误信息可以先用strerror()把错误码转换成错误信息再打印。 3 线程的创建
pthread_create函数
#include pthread.h
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg );
功能创建一个线程。
参数thread线程标识符地址。attr线程属性结构体地址通常设置为 NULL。start_routine线程函数的入口地址。arg传给线程函数的参数。
返回值成功0失败非 0参考代码
#include ../tou.h
void *func(void *arg)
{printf(子线程:%ld被执行\n, pthread_self());if (arg NULL){printf(参数为空\n);}return NULL;
}
void *func2(void *arg)
{printf(子线程:%ld被执行\n, pthread_self());if (arg ! NULL){int arg_t (int)(long)arg; // 把八个字节的arg 给四个字节的arg_t .long类型占八字节printf(传入的参数为%d\n, arg_t);}return NULL;
}
int main()
{pthread_t tid -1;int ret -1;// 传入空的线程描述符和空的线程函数参数ret pthread_create(tid, NULL, func, NULL); // 如果ret 0 就说明创建成功if (ret 0){printf(2子线程:%ld被执行\n, tid);};// 传入空的线程描述符和 一个线程参数pthread_t tid2 -1;ret pthread_create(tid2, NULL, func2, (void *)200); // void* 占8字节if (ret 0) // 如果ret 0 就说明创建成功{printf(子线程:%ld被执行\n, tid2);}; // 传入空的线程描述符和空的线程函数参数pthread_join(tid, NULL);pthread_join(tid2, NULL);
}创建多个线程
传地址作为函数参数
#includestdio.h
#includestring.h
#includestdlib.h
#includeunistd.h
#includeassert.h
#includepthread.hvoid* pthread_fun(void* arg)
{int index *(int*)arg;int i 0;for(; i 5; i){printf(index %d\n,index);sleep(1);}
}int main()
{pthread_t id[5];int i 0;for(; i 5; i){pthread_create(id[i],NULL,pthread_fun,(void*)i);}for(i 0; i 5; i){pthread_join(id[i],NULL);}exit(0);
}
运行结果 或者 为什么会产生这种情况呢线程并发问题。 这是因为我们向pthread_fun传入i的地址。首先来说说为什么会出现多个线程拿到同一个i的值。线程创建在计算机中需要很多个步骤我们进入for循环传入i的地址后就去进行下一个for循环创建的线程还没有从地址中获取打印i的值主函数就继续创建后面的线程了导致多个线程并发拿到同一个i值而且不是创建该线程的时候i的值。 注意到打印第一个运行结果都是打印0这是因为主函数第一个for循环已经结束了后面一个for循环将i又置为0而这些线程在主函数第一个for循环执行的时候都没有回获取i的值打印直到下一个for循环这些线程才获取i值打印所以打印出来 都是0。 传值作为函数参数
#includestdio.h
#includestring.h
#includestdlib.h
#includeunistd.h
#includeassert.h
#includepthread.hvoid* pthread_fun(void* arg)
{int index (int) arg;int i 0;for(; i 5; i){printf(index %d\n,index);sleep(1);}
}int main()
{pthread_t id[5];int i 0;for(; i 5; i){pthread_create(id[i],NULL,pthread_fun,(void*)i);}for(i 0; i 5; i){pthread_join(id[i],NULL);}exit(0);
}
4 线程共享资源的验证
共享数据段
#include ../tou.h// 创建一个全局变量
int num 100;
void *func(void *arg)
{printf(begin func\n);num;printf(end func\n);
}
int main()
{pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, NULL);pthread_join(tid, NULL);if (ret 0){printf(线程创建成功,num 为:%d\n , num);}
}共享堆空间
#include ../tou.hvoid *func(void *arg)
{int *pn (int *)arg;printf(begin func\n);(*pn);printf(end func\n);
}
int main()
{// 创建堆空间int *p NULL;p malloc(sizeof(int));if (NULL p){printf(分配失败);exit(1);}// 对堆空间进行初始化memset(p, 0, sizeof(int));// 放入数据到该堆空间*p 828;pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, (void *)p);pthread_join(tid, NULL);if (ret 0){printf(线程创建成功,*p 为:%d\n , *p);}
}5 线程资源回收
pthread_join函数 #include pthread.h
int pthread_join(pthread_t thread, void **retval);
功能等待线程结束此函数会阻塞并回收线程资源类似进程的 wait() 函数。如果线程已经结束那么该函数会立即返回。
参数thread被等待的线程号。retval用来存储线程退出状态的指针的地址。
返回值成功0失败非 0pthread_join得到的终止状态是不同的总结如下
1 如果thread线程通过return返回retval所指向的单元里存放的是thread线程函数的返回值2如果thread线程被别的线程调用pthread_cance异常终止掉 reuva所指向的单元里存放的是常数PTHREAD_CANCELED。3如果thread线程是自己调用pthread_exit终止的 reuvaI所指向的单元存放的是传给pthread_exit的参数。
参考代码 #include ../tou.hvoid *func(void *arg)
{int ret 999;pthread_exit((void *)(long)ret);return NULL;
}void *func2(void *arg)
{int ret 888;return ((void *)(long)ret);
}int main()
{pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, NULL);// 定义一个变量来存储线程退出状态的指针的地址void *p NULL;// todo 演示 pthread_exit()pthread_join(tid, p); // 这里是void **类型 // 参见 https://www.coder.work/article/1566260if (ret 0){printf(线程创建成功);printf(*p :%d, (int)(long)p);}printf(\n\n\n);// todo 演示直接返回ret pthread_create(tid, NULL, func2, NULL);pthread_join(tid, p); // 这里是void **类型 // 参见 https://www.coder.work/article/1566260if (ret 0){printf(线程创建成功);printf(*p :%d\n, (int)(long)p);}
}pthread_cancel 后面线程取消再演示 6 线程分离 一般情况下线程终止后其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态这样的线程一旦终止就立刻回收它占用的所有资源而不保留终止状态。 不能对一个已经处于detach状态的线程调用pthread_join这样的调用将返回EINVAL错误。也就是说如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。 pthread_detach函数
#include pthread.h
int pthread_detach(pthread_t thread);
功能使调用线程与当前进程分离分离后不代表此线程不依赖与当前进程线程分离的目的是将线程资源的回收工作交由系统自动来完成也就是说当被分离的线程结束之后系统会自动回收它的资源。所以此函数不会阻塞。
参数thread线程号。
返回值成功0失败非0参考代码
#include ../tou.hvoid *func(void *arg)
{sleep(5);return NULL;
}
int main()
{pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, NULL);if (ret 0){printf(线程创建成功\n);}
#if 0 // 此分支的代码会阻塞5s,再结束主线程ret pthread_join(tid, NULL);if (ret 0){printf(阻塞成功\n);}#endif
#if 1// 此分支,主线程会立即结束ret pthread_detach(tid);if (ret 0){printf(分离成功\n);}#endifprintf(main end...\n\n);
}7 线程退出
在进程中我们可以调用exit函数或_exit函数来结束进程在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
线程从执行函数中返回。线程调用pthread_exit退出线程。线程可以被同一进程中的其它线程取消
pthread_exit函数
pthread_exit函数#include pthread.hvoid pthread_exit(void *retval);
功能退出调用线程。一个进程中的多个线程是共享该进程的数据段因此通常线程退出后所占用的资源并不会释放。
参数retval存储线程退出状态的指针。
返回值无 参考代码
#include ../tou.h// void *func(void *arg)//todo 使用exit(0)
// {
// printf(begin func\n);
// exit(0); //线程和主进程进程直接退出
// printf(end func\n);
// }void *func(void *arg) // todo 使用 phread_exit(void*retval)
{printf(begin func\n);pthread_exit(NULL);printf(end func\n);
}void *func(void *arg) // todo 使用 return NULL
{printf(begin func\n);return NULL; //等同于使用pthread_exit()printf(end func\n);
}int main()
{pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, NULL);if (ret 0){printf(线程创建成功\n);}pthread_join(tid, NULL);printf(主线程开始睡眠\n);sleep(5);printf(主线程结束睡眠\n);
}8 线程取消
pthread_calcel函数 #include pthread.hint pthread_cancel(pthread_t thread);
功能杀死(取消)线程
参数thread : 目标线程ID。
返回值成功0失败出错编号 注意线程的取消并不是实时的而又一定的延时。需要等待线程到达某个取消点(检查点)。 类似于玩游戏存档必须到达指定的场所(存档点如客栈、仓库、城里等)才能存储进度。 杀死线程也不是立刻就能完成必须要到达取消点。 取消点是线程检查是否被取消并按请求进行动作的一个位置。通常是一些系统调用creatopenpauseclosereadwrite… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。 可粗略认为一个系统调用(进入内核)即为一个取消点。 参考代码 线程取消,线程可以被同一进程中的其它线程取消 #include ../tou.h
void *func(void *arg) // todo 使用 return NULL
{printf(begin func\n);while (1){sleep(1);printf(I am func \n);}printf(end func\n);
}
void *func2(void *arg) // todo 使用 return NULL
{pthread_t tid (pthread_t)arg;printf(begin func2\n);sleep(5); // 先让子线程活5s,在取消它int ret pthread_cancel(tid);if (ret 0){printf(线程取消成功\n);}printf(end func2\n);
}
int main()
{pthread_t tid;bzero(tid, sizeof(tid)); // todo 初始化 线程号// todo 创建线程int ret pthread_create(tid, NULL, func, NULL);if (ret 0){printf(func线程创建成功\n);}pthread_t tid2;ret pthread_create(tid2, NULL, func2, (void *)tid);if (ret 0){printf(func2线程创建成功\n);}ret pthread_join(tid2, NULL);if (ret ! 0){printf(等待func2线程失败\n);return 0;}void *mess NULL; // 获取线程被强制取消之后的状态// 获取已终止线程的返回值ret pthread_join(tid, mess);if (ret ! 0){printf(等待func线程失败\n);return 0;}// 如果线程被强制终止其返回值为 PTHREAD_CANCELEDif (mess PTHREAD_CANCELED){printf(func 线程被强制终止\n);}else{printf(func error\n);}
}无效的线程取消
参考代码
#include stdio.h
#include pthread.h
#include stdlib.h
#include unistd.h //调用 sleep() 函数
void * thread_Fun(void * arg) {printf(新建线程开始执行\n);//插入无限循环的代码测试 pthread_cancel()函数的有效性while(1);
}
int main()
{pthread_t myThread;void * mess;int value;int res;res pthread_create(myThread, NULL, thread_Fun, NULL);if (res ! 0) {printf(线程创建失败\n);return 0;}sleep(1);//令 myThread 线程终止执行res pthread_cancel(myThread);if (res ! 0) {printf(终止 myThread 线程失败\n);return 0;}printf(等待 myThread 线程执行结束\n);res pthread_join(myThread, mess);if (res ! 0) {printf(等待线程失败\n);return 0;}if (mess PTHREAD_CANCELED) {printf(myThread 线程被强制终止\n);}else {printf(error\n);}return 0;
} 程序中主线程 main() 函数试图调用 pthread_cancel() 函数终止 myThread 线程执行。从运行结果不难发现pthread_cancel() 函数成功发送了 Cancel 信号但目标线程仍在执行。
也就是说接收到 Cancel 信号的目标线程并没有立即处理该信号或者说目标线程根本没有理会此信号。解决类似的问题我们就需要搞清楚目标线程对 Cancel 信号的处理机制。
根据上节的内容pthread_join会阻塞调用它的线程因此程序在执行完printf(“等待 myThread 线程执行结束\n”);后就会一直阻塞在res pthread_join(myThread, mess);等待目标线程myThread执行完毕
线程对Cancel信号的处理
对于默认属性的线程当有线程借助 pthread_cancel() 函数向它发送 Cancel 信号时它并不会立即结束执行而是选择在一个适当的时机结束执行。
所谓适当的时机POSIX 标准中规定当线程执行一些特殊的函数时会响应 Cancel 信号并终止执行比如常见的 pthread_join()、pthread_testcancel()、sleep()、system() 等POSIX 标准称此类函数为“cancellation points”中文可译为“取消点”。 POSIX 标准中明确列举了所有可以作为取消点的函数这里不再一一罗列感兴趣的读者可以自行查阅 POSIX 标准手册。 此外pthread.h 头文件还提供有 pthread_setcancelstate() 和 pthread_setcanceltype() 这两个函数我们可以手动修改目标线程处理 Cancel 信号的方式。 1线程取消函数pthread_cancel() 2设置线程取消响应—是否响应取消信号 3设置响应取消信号的类型----立即响应、延时响应 代码段
参考代码(修复无效的线程取消)
#include stdio.h
#include errno.h
#include pthread.h
#include string.h
#include stdlib.h
#include unistd.h
struct Data
{char pthread_name[10];
};void *Pthread_Task(void *arg)
{//设置线程取消状态---接受取消请求int pthread_setcancelstate_ret pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);if (pthread_setcancelstate_ret ! 0){perror(pthread_setcancelstate);exit(-1);}while (1){struct Data *p (struct Data *)arg;printf(%s\n, p-pthread_name);sleep(1);}pthread_exit(NULL);
}int main()
{pthread_t pid;struct Data d1;memset(d1, 0, sizeof(d1));strcpy(d1.pthread_name, hello);//创建线程int ret pthread_create(pid, NULL, Pthread_Task, (void *)d1);if (ret ! 0){perror(pthread_create);exit(-1);}printf(5s之后发送取消请求\n);sleep(5);//取消线程pthread_cancel(pid);pause();return 0;
}
9 线程清理
有时候我们希望线程退出时能够自动的执行某些函数为了能达到此目的OS 提供了两个函数帮我们完成这个功能
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
void pthread_cleanup_pop(int execute);使用方法
如果想要你的线程在退出时能够执行清理函数你需要使用 pthread_cleanup_push 对你的清理函数进行注册如下
void clean(void *arg) {// ...
}void *th_fn(void *arg) {// push 和 pop 必须对对出现pthread_cleanup_push(clean, /* 清理函数 clean 的参数*/);// ...pthread_cleanup_pop(1);
}在 Linux 中pthread_cleanup_push 和 pthread_cleanup_pop 这两个函数是通过宏来做的pthread_cleanup_push 被替换成以左花括号 { 为开头的一段代码而 pthread_cleanup_pop 被替换成以右花括号 } 结尾的一段代码这就意味着这两个函数必须要成对出现才能将左右花括号匹配上否则就出现编译错误。 有些平台可能不是使用宏来实现就算不成对也没什么关系。
线程清理函数调用
有三种情况线程清理函数会被调用 1、线程还未执行 pthread_cleanup_pop 前被 pthread_cancel 取消 2、线程还未执行 pthread_cleanup_pop 前主动执行 pthread_exit 终止 3、线程执行 pthread_cleanup_pop且 pthread_cleanup_pop 的参数不为 0
注意如果线程还未执行 pthread_cleanup_pop 前通过 return 返回是不会执行清理函数的。
线程清理例程
程序 clean 需要传入两个参数第 1 个参数表示是否提前返回在执行 pthread_cleanup_pop 前返回第 2 个参数表示 pthread_cleanup_pop 的参数。所以有 4 种组合情况。
#include unistd.h
#include pthread.h
#include stdio.h
#include string.hint excute;void cleanup(void* arg) {printf(cleanup: %s\n, (char*)arg);
}void* th_fn1(void* arg) {puts(thread 1 starting);pthread_cleanup_push(cleanup, 线程 1 清理者 1 号);pthread_cleanup_push(cleanup, 线程 1 清理者 2 号);if (arg) {printf(线程 1 提前退出\n);return (void*)1;}pthread_cleanup_pop(excute);pthread_cleanup_pop(excute);printf(线程 1 正常退出\n);return (void*)10;
}void* th_fn2(void* arg) {puts(thread 2 starting);pthread_cleanup_push(cleanup, 线程 2 清理者 1 号);pthread_cleanup_push(cleanup, 线程 2 清理者 2 号);if (arg) {printf(线程 2 提前退出\n);pthread_exit((void*)2);}pthread_cleanup_pop(excute);pthread_cleanup_pop(excute);printf(线程 2 正常退出\n);pthread_exit((void*)20);
}int main(int argc, char* argv[]) {if (argc 3) {printf(Usage: %s arg 0|1 excute 0|1\n, argv[0]);return -1;}pthread_t tid1, tid2;int err;void* ret;void *arg NULL;excute 0;arg (void*)atoi(argv[1]);excute atoi(argv[2]);err pthread_create(tid1, NULL, th_fn1, arg);err pthread_create(tid2, NULL, th_fn2, arg);err pthread_join(tid1, ret);printf(thread 1 exit code %d\n, (int)ret);err pthread_join(tid2, ret);printf(thread 2 exit code %d\n, (int)ret);return 0;
}结果可以看到 当 clean 程序中的线程正常返回时只有 pthread_cleanup_pop 的参数非 0 时才会正常执行清理函数。 当 clean 程序中的线程在执行 pthread_cleanup_pop 前时使用 pthread_exit 退出时清理函数才会被执行和pthread_cleanup_pop 的参数没有关系。而使用 return 返回的线程 1 并不会执行清理函数。清理函数的执行顺序是按照注册时候相反的顺序执行的。 注意在有些系统中(如Mac OS X)提前终止可能会出现段错误。 8 线程属性 Linux下线程的属性是可以根据实际项目需要进行设置之前我们讨论的线程都是采用线程的默认属性默认属性已经可以解决绝大多数开发时遇到的问题。 如我们对程序的性能提出更高的要求那么需要设置线程属性比如可以通过设置线程栈的大小来降低内存的使用增加最大线程个数。 typedef struct
{int etachstate; //线程的分离状态int schedpolicy; //线程调度策略struct sched_param schedparam; //线程的调度参数int inheritsched; //线程的继承性int scope; //线程的作用域size_t guardsize; //线程栈末尾的警戒缓冲区大小int stackaddr_set; //线程的栈设置void* stackaddr; //线程栈的位置size_t stacksize; //线程栈的大小
} pthread_attr_t;主要结构体成员
\1) 线程分离状态
\2) 线程栈大小默认平均分配
\3) 线程栈警戒缓冲区大小位于栈末尾
\4) 线程栈最低地址
属性值不能直接设置须使用相关函数进行操作初始化的函数为pthread_attr_init这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。线程属性主要包括如下属性作用域scope、栈尺寸stack size、栈地址stack address、优先级priority、分离的状态detached state、调度策略和参数scheduling policy and parameters。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
8.2 线程属性初始化和销毁
#include pthread.h
int pthread_attr_init(pthread_attr_t *attr);
功能初始化线程属性函数注意应先初始化线程属性再pthread_create创建线程
参数attr线程属性结构体
返回值成功0失败错误号
int pthread_attr_destroy(pthread_attr_t *attr);
功能销毁线程属性所占用的资源函数
参数attr线程属性结构体
返回值成功0失败错误号8.3 线程分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离状态线程的默认属性是非分离状态这种情况下原有的线程等待创建的线程结束。只有当pthread_join()函数返回时创建的线程才算终止才能释放自己占用的系统资源。分离状态分离线程没有被其他的线程所等待自己运行结束了线程也就终止了马上释放系统资源。应该根据自己的需要选择适当的分离状态。
相关函数 #include pthread.h
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能设置线程分离状态
参数attr已初始化的线程属性detachstate 分离状态PTHREAD_CREATE_DETACHED分离线程PTHREAD_CREATE_JOINABLE非分离线程
返回值成功0失败非0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能获取线程分离状态
参数attr已初始化的线程属性detachstate 分离状态PTHREAD_CREATE_DETACHED分离线程PTHREAD _CREATE_JOINABLE非分离线程
返回值成功0失败非0
这里要注意的一点是如果设置一个线程为分离线程而这个线程运行又非常快它很可能在pthread_create函数返回之前就终止了它终止以后就可能将线程号和系统资源移交给其他的线程使用这样调用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数让这个线程等待一会儿留出足够的时间让函数pthread_create返回。
设置一段等待时间是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数它们是使整个进程睡眠并不能解决线程同步的问题。
8.4 线程栈地址
POSIX.1定义了两个常量来检测系统是否支持栈属性
_POSIX_THREAD_ATTR_STACKADDR_POSIX_THREAD_ATTR_STACKSIZE
也可以给sysconf函数传递来进行检测
_SC_THREAD_ATTR_STACKADDR_SC_THREAD_ATTR_STACKSIZE
当进程栈地址空间不够用时指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。
#include pthread.h
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
功能设置线程的栈地址
参数attr指向一个线程属性的指针stackaddr内存首地址stacksize返回线程的堆栈大小
返回值成功0失败错误号
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
功能获取线程的栈地址
参数attr指向一个线程属性的指针stackaddr返回获取的栈地址stacksize返回获取的栈大小
返回值成功0失败错误号
8.5 线程栈大小
当系统中有很多线程时可能需要减小每个线程栈的默认大小防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时可能需要增大线程栈的默认大小。
#include pthread.h
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
功能设置线程的栈大小
参数attr指向一个线程属性的指针stacksize线程的堆栈大小
返回值成功0失败错误号
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
功能获取线程的栈大小
参数 attr指向一个线程属性的指针stacksize返回线程的堆栈大小
返回值成功0失败错误号
8.6 综合参考程序
#define SIZE 0x100000void *th_fun(void *arg)
{while (1){sleep(1);}
}int main()
{pthread_t tid;int err, detachstate, i 1;pthread_attr_t attr;size_t stacksize;void *stackaddr;pthread_attr_init(attr); //线程属性初始化pthread_attr_getstack(attr, stackaddr, stacksize); //获取线程的栈地址pthread_attr_getdetachstate(attr, detachstate); //获取线程分离状态if (detachstate PTHREAD_CREATE_DETACHED){printf(thread detached\n);}else if (detachstate PTHREAD_CREATE_JOINABLE){printf(thread join\n);}else{printf(thread unknown\n);}pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); //设置分离状态while (1) {stackaddr malloc(SIZE);if (stackaddr NULL) {perror(malloc);exit(1);}stacksize SIZE;pthread_attr_setstack(attr, stackaddr, stacksize); //设置线程的栈地址err pthread_create(tid, attr, th_fun, NULL); //创建线程if (err ! 0) {printf(%s\n, strerror(err));exit(1);}printf(%d\n, i);}pthread_attr_destroy(attr); //销毁线程属性所占用的资源函数return 0;
}8.7 线程使用注意事项
\1) 主线程退出其他线程不退出主线程应调用pthread_exit
\2) 避免僵尸线程
a) pthread_join
b) pthread_detach
c) pthread_create指定分离属性
被join线程可能在join函数返回前就释放完自己的所有内存资源所以不应当返回被回收线程栈中的值;
\3) malloc和mmap申请的内存可以被其他线程释放
\4) 应避免在多线程模型中调用fork除非马上exec子进程中只有调用fork的线程存在其他线程t在子进程中均pthread_exit
\5) 信号的复杂语义很难和多线程共存应避免在多线程引入信号机制
补充 : pthread_join的第二个参数
在看pthread相关遇到了pthread_join函数
#include pthread.h
int pthread_join(pthread_t pthread_id, void** retval);这个函数的第二个参数为什么是void**有啥用
首先这个函数的用途是什么 manpage给出的解释
The pthread_join() function waits for the thread specified by threadto terminate. If that thread has already terminated, thenpthread_join() returns immediately. The thread specified by threadmust be joinable.If retval is not NULL, then pthread_join() copies the exit status ofthe target thread (i.e., the value that the target thread supplied topthread_exit(3)) into the location pointed to by retval. If thetarget thread was canceled, then PTHREAD_CANCELED is placed in thelocation pointed to by retval.If multiple threads simultaneously try to join with the same thread,the results are undefined. If the thread calling pthread_join() iscanceled, then the target thread will remain joinable (i.e., it willnot be detached).意图很明显以阻塞的方式等待指定线程可joinable结束。成功返回0失败返回错误号。
一个线程的结束有两种方式一种是正常结束。一种是使用pthread_exit。对于使用pthread_exit结束的线程可以返回一个status给主线程。
void pthread_exit(void* retval);那么它的参数就和pthread_join第二个参数就对应上了。看一下怎么用!
//错误演示
#include error.h
#include unistd.h
#include fcntl.h
#include stdlib.h
#include stdio.h
#include string.h
#include errno.h
#include unistd.h#include pthread.hstruct my_threadfunc_error
{int a;int b;
};void* StartFunction(void* arg)
{my_threadfunc_error var{1,2};pthread_exit((void*)var);return NULL;
}int main(int argc, char* argv[])
{pthread_t my_pthread_t 0;if (0 ! pthread_create(my_pthread_t, NULL, StartFunction, NULL)){printf(pthread_create error!\n);return -1;}void** p NULL;if (0 ! pthread_join(my_pthread_t, p)){printf(pthread_join error!\n);return -1;}my_threadfunc_error* var (my_threadfunc_error*)(*p);//11printf(var.a %d, var.b %d\n, var-a, var-b);return 0;
}上面代码在线程函数中返回的是一个栈上的变量的地址所在在代码运行到my_threadfunc_error* var (my_threadfunc_error*)(*p);//11的时候就出现段错误了。很好理解因为线程函数结束线程的栈也会被回收var的空间也被清理了所以使用未知的内存发生段错误正常。
既然如此那么使用malloc或者全局变量即可处理这种问题!
#include error.h
#include unistd.h
#include fcntl.h
#include stdlib.h
#include stdio.h
#include string.h
#include errno.h
#include unistd.h#include pthread.hstruct my_threadfunc_error
{int a;int b;
};void* StartFunction(void* arg)
{my_threadfunc_error* var (my_threadfunc_error*)malloc(sizeof(my_threadfunc_error));var-a 100;var-b 200;pthread_exit((void*)var);return NULL;
}int main(int argc, char* argv[])
{pthread_t my_pthread_t 0;if (0 ! pthread_create(my_pthread_t, NULL, StartFunction, NULL)){printf(pthread_create error!\n);return -1;}void* p NULL;if (0 ! pthread_join(my_pthread_t, p)){printf(pthread_join error!\n);return -1;}my_threadfunc_error* var (my_threadfunc_error*)(p);printf(var.a %d, var.b %d\n, var-a, var-b);free(var);return 0;
}{ printf(“pthread_create error!\n”); return -1; } void** p NULL; if (0 ! pthread_join(my_pthread_t, p)) { printf(“pthread_join error!\n”); return -1; } my_threadfunc_error* var (my_threadfunc_error*)(*p);//11 printf(“var.a %d, var.b %d\n”, var-a, var-b); return 0; } 上面代码在线程函数中返回的是一个栈上的变量的地址所在在代码运行到my_threadfunc_error* var (my_threadfunc_error*)(*p);//11的时候就出现段错误了。很好理解因为线程函数结束线程的栈也会被回收var的空间也被清理了所以使用未知的内存发生段错误正常。既然如此那么使用malloc或者全局变量即可处理这种问题!c
#include error.h
#include unistd.h
#include fcntl.h
#include stdlib.h
#include stdio.h
#include string.h
#include errno.h
#include unistd.h#include pthread.hstruct my_threadfunc_error
{int a;int b;
};void* StartFunction(void* arg)
{my_threadfunc_error* var (my_threadfunc_error*)malloc(sizeof(my_threadfunc_error));var-a 100;var-b 200;pthread_exit((void*)var);return NULL;
}int main(int argc, char* argv[])
{pthread_t my_pthread_t 0;if (0 ! pthread_create(my_pthread_t, NULL, StartFunction, NULL)){printf(pthread_create error!\n);return -1;}void* p NULL;if (0 ! pthread_join(my_pthread_t, p)){printf(pthread_join error!\n);return -1;}my_threadfunc_error* var (my_threadfunc_error*)(p);printf(var.a %d, var.b %d\n, var-a, var-b);free(var);return 0;
}
文章转载自: http://www.morning.fzwf.cn.gov.cn.fzwf.cn http://www.morning.cnfxr.cn.gov.cn.cnfxr.cn http://www.morning.gtnyq.cn.gov.cn.gtnyq.cn http://www.morning.pzlcd.cn.gov.cn.pzlcd.cn http://www.morning.wdwfm.cn.gov.cn.wdwfm.cn http://www.morning.bbjw.cn.gov.cn.bbjw.cn http://www.morning.pqryw.cn.gov.cn.pqryw.cn http://www.morning.qmbpy.cn.gov.cn.qmbpy.cn http://www.morning.yswxq.cn.gov.cn.yswxq.cn http://www.morning.qghjc.cn.gov.cn.qghjc.cn http://www.morning.nlgnk.cn.gov.cn.nlgnk.cn http://www.morning.pxwjp.cn.gov.cn.pxwjp.cn http://www.morning.hybmz.cn.gov.cn.hybmz.cn http://www.morning.nyhtf.cn.gov.cn.nyhtf.cn http://www.morning.wgqtj.cn.gov.cn.wgqtj.cn http://www.morning.lfpdc.cn.gov.cn.lfpdc.cn http://www.morning.ynrzf.cn.gov.cn.ynrzf.cn http://www.morning.srmpc.cn.gov.cn.srmpc.cn http://www.morning.xfxnq.cn.gov.cn.xfxnq.cn http://www.morning.wiitw.com.gov.cn.wiitw.com http://www.morning.ai-wang.cn.gov.cn.ai-wang.cn http://www.morning.qwfl.cn.gov.cn.qwfl.cn http://www.morning.jzdfc.cn.gov.cn.jzdfc.cn http://www.morning.dmlsk.cn.gov.cn.dmlsk.cn http://www.morning.pbmkh.cn.gov.cn.pbmkh.cn http://www.morning.ksgjn.cn.gov.cn.ksgjn.cn http://www.morning.lkthj.cn.gov.cn.lkthj.cn http://www.morning.xwlhc.cn.gov.cn.xwlhc.cn http://www.morning.tkyry.cn.gov.cn.tkyry.cn http://www.morning.cpkcq.cn.gov.cn.cpkcq.cn http://www.morning.ynbyk.cn.gov.cn.ynbyk.cn http://www.morning.fynkt.cn.gov.cn.fynkt.cn http://www.morning.bszmy.cn.gov.cn.bszmy.cn http://www.morning.snbrs.cn.gov.cn.snbrs.cn http://www.morning.kztpn.cn.gov.cn.kztpn.cn http://www.morning.rfpb.cn.gov.cn.rfpb.cn http://www.morning.rgkd.cn.gov.cn.rgkd.cn http://www.morning.bpmth.cn.gov.cn.bpmth.cn http://www.morning.wqrdx.cn.gov.cn.wqrdx.cn http://www.morning.dtnyl.cn.gov.cn.dtnyl.cn http://www.morning.wmfh.cn.gov.cn.wmfh.cn http://www.morning.wmdbn.cn.gov.cn.wmdbn.cn http://www.morning.xsrnr.cn.gov.cn.xsrnr.cn http://www.morning.nlryq.cn.gov.cn.nlryq.cn http://www.morning.tmxfn.cn.gov.cn.tmxfn.cn http://www.morning.mgkb.cn.gov.cn.mgkb.cn http://www.morning.lwzgn.cn.gov.cn.lwzgn.cn http://www.morning.nlffl.cn.gov.cn.nlffl.cn http://www.morning.lclpj.cn.gov.cn.lclpj.cn http://www.morning.kgxrq.cn.gov.cn.kgxrq.cn http://www.morning.sgnxl.cn.gov.cn.sgnxl.cn http://www.morning.hnhsym.cn.gov.cn.hnhsym.cn http://www.morning.bqts.cn.gov.cn.bqts.cn http://www.morning.jfsbs.cn.gov.cn.jfsbs.cn http://www.morning.dgpxp.cn.gov.cn.dgpxp.cn http://www.morning.qwfq.cn.gov.cn.qwfq.cn http://www.morning.lsnnc.cn.gov.cn.lsnnc.cn http://www.morning.wbxrl.cn.gov.cn.wbxrl.cn http://www.morning.jxrpn.cn.gov.cn.jxrpn.cn http://www.morning.xhjjs.cn.gov.cn.xhjjs.cn http://www.morning.gtbjc.cn.gov.cn.gtbjc.cn http://www.morning.wqtzs.cn.gov.cn.wqtzs.cn http://www.morning.fylsz.cn.gov.cn.fylsz.cn http://www.morning.fssjw.cn.gov.cn.fssjw.cn http://www.morning.dpjtn.cn.gov.cn.dpjtn.cn http://www.morning.hfrbt.cn.gov.cn.hfrbt.cn http://www.morning.sxbgc.cn.gov.cn.sxbgc.cn http://www.morning.jpjxb.cn.gov.cn.jpjxb.cn http://www.morning.xnzmc.cn.gov.cn.xnzmc.cn http://www.morning.mtmnk.cn.gov.cn.mtmnk.cn http://www.morning.tgczj.cn.gov.cn.tgczj.cn http://www.morning.pmptm.cn.gov.cn.pmptm.cn http://www.morning.ybhrb.cn.gov.cn.ybhrb.cn http://www.morning.hwbmn.cn.gov.cn.hwbmn.cn http://www.morning.fkmrj.cn.gov.cn.fkmrj.cn http://www.morning.mxhgy.cn.gov.cn.mxhgy.cn http://www.morning.bqpgq.cn.gov.cn.bqpgq.cn http://www.morning.rgnp.cn.gov.cn.rgnp.cn http://www.morning.yrdn.cn.gov.cn.yrdn.cn http://www.morning.fxzgw.com.gov.cn.fxzgw.com