Linux多线程(二)(线程等待,退出)
1. 线程的等待退出
1.1. 等待线程退出
线程从入口点函数自然返回,或者主动调用pthread_exit()函数,都可以让线程正常终止
线程从入口点函数自然返回时,函数返回值可以被其它线程用pthread_join函数获取
pthread_join原型为:
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
1. 该函数是一个阻塞函数,一直等到参数th指定的线程返回;与多进程中的wait或waitpid类似。
thread_return是一个传出参数,接收线程函数的返回值。如果线程通过调用pthread_exit()终止,则pthread_exit()中的参数相当于自然返回值,照样可以被其它线程用pthread_join获取到。
Example:返回值的例子
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *ThreadFunc(void *pArg)
{
int iArg = (int)pArg; //将void*转换为int
sleep(iArg);
if(iArg < 3)
return (void *)(iArg*2);
else
pthread_exit((void *)(iArg*2)); //和reaturn达到的效果一样,都可以用于正常返回
}
int main()
{
pthread_t thdId;
int iRet = 0;
pthread_create(&thdId, NULL, ThreadFunc, (void *)2 ); //传递参数值为2
pthread_join(thdId,(void **)&iRet); //接收子线程的返回值
printf("The first child thread ret is:%d\n",iRet);
pthread_create(&thdId, NULL, ThreadFunc, (void *)4 );
pthread_join(thdId,(void **)&iRet);
printf("The second child thread ret is:%d\n",iRet);
return 0;
}
2. 该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果th线程类型并不是自动清理资源类型的,则th线程退出后,线程本身的资源必须通过其它线程调用pthread_join来清除,这相当于多进程程序中的waitpid。
Example:子线程释放空间
#include <stdio.h>
#include <pthread.h>
#include <malloc.h>
void* threadfunc(void *args)
{
char *p = (char*)malloc(10); //自己分配了内存
int i = 0;
for(; i < 10; i++)
{
printf("hello,my name is wangxiao!\n");
sleep(1);
}
free(p); //如果父线程中没有调用pthread_cancel,此处可以执行
printf("p is freed\n");
pthread_exit((void*)3);
}
int main()
{
pthread_t pthid;
pthread_create(&pthid, NULL, threadfunc, NULL);
int i = 1;
for(; i < 5; i++) //父线程的运行次数比子线程的要少,当父线程结束的时候,如果没有pthread_join函数等待子线程执行的话,子线程也会退出。
{
printf("hello,nice to meet you!\n");
sleep(1);
// if(i % 3 == 0)
// pthread_cancel(pthid); //表示当i%3==0的时候就取消子线程,该函数将导致子线程直接退出,不会执行上面紫色的free部分的代码,即释放空间失败。要想释放指针类型的变量p,此时必须要用pthread_cleanup_push和pthread_cleanup_pop函数释放空间,见后面的例子
}
int retvalue = 0;
pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间,并获取子线程的返回值
printf("return value is :%d\n",retvalue);
return 0;
}
1.2. 线程的取消
线程也可以被其它线程杀掉,在Linux中的说法是一个线程被另一个线程取消(cancel)。
线程取消的方法是一个线程向目标线程发cancel信号,但是如何处理cancel信号则由目标线程自己决定,目标线程或者忽略、或者立即终止、或者继续运行至cancelation-point(取消点)后终止。
取消点:
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于Linux线程库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
但是从RedHat9.0的实际测试来看,至少有些C库函数的阻塞函数是取消点,如read(),getchar()等,而sleep()函数不管线程是否设置了pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL),都起到取消点作用。总之,线程的取消一方面是一个线程强行杀另外一个线程,从程序设计角度看并不是一种好的风格,另一方面目前Linux本身对这方面的支持并不完善,所以在实际应用中应该谨慎使用!!
int pthread_cancel(pthread_t thread); //尽量不要用,linux支持并不完善
1.3. 线程终止清理函数
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理
void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \
{
struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute));
}
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
pthread_cleanup_pop的参数execute如果为非0值,则按栈的顺序注销掉一个原来注册的清理函数,并执行该函数;当pthread_cleanup_pop()函数的参数为0时,仅仅在线程调用pthread_exit函数或者其它线程对本线程调用pthread_cancel函数时,才在弹出“清理函数”的同时执行该“清理函数”。
示例:
#include <stdio.h>
#include <pthread.h>
void CleanFunc(void *pArg)
{
printf("CleanFunc(%d)\n",(int)pArg);
}
void *ThreadFunc(void *pArg)
{
pthread_cleanup_push(CleanFunc,(void *)1);
pthread_cleanup_push(CleanFunc,(void *)2);
sleep(2);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
}
int main()
{
pthread_t thdId;
pthread_create(&thdId, NULL, ThreadFunc, (void *)2);
pthread_join(thdId,NULL);
return 0;
}
运行结果为:
CleanFunc(2)
CleanFunc(1)
如果将里面的两次pthread_cleanup_pop(1);改为pthread_cleanup_pop(0);推测一下结果是怎样?
à没有任何输出(此时CleanFunc函数得不到执行)
如果修改为0之后,再在sleep(2)之后添加pthread_exit(NULL);则此时的结果又是如何:
à跟pthread_cleanup_pop(1);实现的结果一样了。
Example:用pthread_cleanup_push和pthread_cleanup_pop来释放子线程分配的内存空间
#include <stdio.h>
#include <pthread.h>
#include <malloc.h>
void freemem(void * args)
{
free(args);
printf("clean up the memory!\n");
}
void* threadfunc(void *args)
{
char *p = (char*)malloc(10); //自己分配了内存
pthread_cleanup_push(freemem,p);
int i = 0;
for(; i < 10; i++)
{
printf("hello,my name is wangxiao!\n");
sleep(1);
}
pthread_exit((void*)3);
pthread_cleanup_pop(0);
}
int main()
{
pthread_t pthid;
pthread_create(&pthid, NULL, threadfunc, NULL);
int i = 1;
for(; i < 5; i++) //父线程的运行次数比子线程的要少,当父线程结束的时候,如果没有pthread_join函数等待子线程执行的话,子线程也会退出,即子线程也只执行了4次。
{
printf("hello,nice to meet you!\n");
sleep(1);
if(i % 3 == 0)
pthread_cancel(pthid); //表示当i%3==0的时候就取消子线程,该函数将导致直接退出,不会执行上面紫色的free部分的代码,即释放空间失败。要想释放指针类型的变量p,必须要用pthread_cleanup_push和pthread_cleanup_pop函数释放空间
}
int retvalue = 0;
pthread_join(pthid,(void**)&retvalue); //等待子线程释放空间,并获取子线程的返回值
printf("return value is :%d\n",retvalue);
return 0;
}
Linux多线程(二)(线程等待,退出)的更多相关文章
- c/c++ 多线程 一个线程等待某种事件发生
多线程 一个线程等待某种事件发生 背景:某个线程在能够完成其任务之前可能需要等待另一个线程完成其任务. 例如:坐夜间列车,为了能够不坐过站, 1,整夜保持清醒,但是这样你就会非常累,不能够睡觉. 2, ...
- Linux多线程编程——线程的创建与退出
POSIX线程标准:该标准定义了创建和操纵线程的一整套API.在类Unix操作系统(Unix.Linux.Mac OS X等)中,都使用Pthreads作为操作系统的线程.Windows操作系统也有其 ...
- C# 多线程(二) 线程同步基础
本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization E ...
- (转)linux多线程,线程的分离与结合
转自:http://www.cnblogs.com/mydomain/archive/2011/08/14/2138454.htm 线程的分离与结合 在任何一个时间点上,线程是可结合的(joi ...
- Linux 多线程编程--线程退出
今天分析项目中进程中虚存一直增长问题,运行10个小时虚存涨到121G ,RSS占用为16G 非常恐怖. Valgrind测试无内存泄漏. 内存32G 64bit系统信息如下: Linux线程使用方式是 ...
- Linux多线程及线程同步简单实例
一.多线程基本概念 1. 线程的基本概念 ① 线程就是轻量级的进程 ②线程和创建他的进程共享代码段.数据段 ③线程拥有自己的栈 2. 在实际应用中,多个线程往往会访问同一数据或资源,为避免线程之间相互 ...
- 并发和多线程(二)--线程安全、synchronized、CAS简介
线程安全性: 当多个线程访问一个类的时候,这个类始终表示出正确的行为,那么这个类是线程安全的. 无状态的对象一定是线程安全的,例如大部分service.dao.Servlet都是无状态的. 线程安全体 ...
- 细说.NET中的多线程 (二 线程池)
上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数 ...
- (原创)JAVA多线程二线程池
一,线程池的介绍 线程池包括一下三种: 线程池名称 创建方法 特点 其他 固定大小线程池 ExecutorService threadpool = Executors.newFixedThreadPo ...
- Linux多线程编程——线程的同步
POSIX信号量 posix信号量不同于IPC中的信号量 常用的posix信号量函数 #include <semaphore.h> int sem_init(sem_t* sem,i ...
随机推荐
- poj 2187
求凸包后枚举凸包上的点 #include <cstdio> #include <cstdlib> #include <cmath> #include <map ...
- #!--->hashbang技术
url中的#! URL 中的 # 本来的用途是跳转到页内锚点.一个 URL 中 # 后的值 (hash tag) 不影响所访问网页的内容,所以搜索引擎在处理仅仅 hash tag 不同的多个 URL ...
- ASP.NET - 演练:创建网页以显示 XML 数据
数据通常是以 XML 格式提供给 Web 应用程序的.但是,XML 数据本质上是分层的,因此您可能希望能够在基于列表的控件中使用 XML 数据,如 GridView 或 DropDownList 控件 ...
- xxx.properties获取方法
1.手动获取加载 Locale locale = Locale.getDefault(); ResourceBundle localResource = ResourceBundle.getBundl ...
- Create Script Template In Edit Mode
很多时候 许多类 的 格式 都是重复的,比如 从配置文件中映射出来的类. 这个时候写一个 类模板 就很节省时间了. Code public static string TestPath = " ...
- no module named firefly.master.master
因为没有安装firefly python setup.py install
- MVC模式在游戏开发的应用
原地址: http://www.cocoachina.com/gamedev/2012/1129/5212.html MVC是三个单词的缩写,分别为:模型(Model).视图(View)和控制Cont ...
- PHP 性能分析第一篇: Xhprof & Xhgui 介绍
[前言]这是国外知名博主 Davey Shafik所撰写的 PHP 应用性能分析系列的第一篇,阅读第二篇可深入了解 xhgui,第三篇则关注于性能调优实践. 什么是性能分析? 性能分析是衡量应用程序在 ...
- 深入浅出ES6(十五):子类 Subclassing
作者 Jason Orendorff github主页 https://github.com/jorendorff 在之前的文章<深入浅出ES6(十三):类 Class>中,我们一起深 ...
- hdu 4061 A Card Game
思路: 分析:假设取的牌顺序是一个序列,那么这种序列在末尾为1时是和取牌序列一一对应的,且是符合“游戏结束时牌恰好被取完”的一种情况. 简证:1.在序列中,任一数 i 的后一个数 j 是必然要放在第 ...