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;

}

时间: 2024-12-15 18:20:44

Linux多线程(二)(线程等待,退出)的相关文章

Linux多线程程序设计-----线程标示

#include<pthread.h> pthread_t pthread_self(void) 功能:获取调用线程的thread identifer 例如:thread_id.c 运行结果: 清除: 线程终止有两种情况:正常终止和非正常终止 当某段代码可能出现不可预料的终止时,可以用pthread_cleanup_push和pthread_cleanup_pop将代码保护起来. 凡是用push和pop框起来的代码,不管是正常退出还是异常退出,在退出之前,都将执行push所指定的清除函数.包括

C# 多线程(二) 线程同步基础

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

C# 多线程(二) 线程同步基础(上)

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

(转)linux多线程,线程的分离与结合

转自:http://www.cnblogs.com/mydomain/archive/2011/08/14/2138454.htm 线程的分离与结合     在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached).一个可结合的线程能够被其他线程收回其资源和杀死:在被其他线程回收之前,它的存储器资源(如栈)是不释放的.相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放. 设置线程分离状态的函数为pthread_attr_set

多线程,线程的退出顺序

1:主进程退出时,不管子.孙...线程都会退出.但是对子线程创建的线程来说,比如下面的的子线程创建了孙线程,子线程先退出,孙线程没有退出 #include <pthread.h> #include <stdio.h> void* fun(void* v) { sleep(10); printf("grandson thread exit\n"); } void* thrd_start_routine(void* v) { pthread_t thd; pthre

Linux 多线程编程--线程退出

今天分析项目中进程中虚存一直增长问题,运行10个小时虚存涨到121G ,RSS占用为16G 非常恐怖. 顺便查了下Linux单进程能创建线程的上限,以及相关内容.内存32G 64bit系统信息如下: Linux线程使用方式是主进程依据请求的多少动态创建和退出线程.通过pmap -x pid查看进程内个部分内存分配情况: 发现大量如下占用: 通过查询可知[anon]为未实际分配的内存,即虚存:通过如下命令计算出一共有11946个,每个对应10M总大小为 119460≍119G 1 cat proc

Linux多线程编程——线程的同步与互斥

前言:无论是多线程编程还是多进程编程,控制好不同线程或不同进程之间同步和互斥问题是非常有必要的.同步是多个进程或线程共同完成某个任务,举例说,一个缓冲区的生产者和消费者问题,当生产者生产了一个商品时,等待的消费者就获得了一个消息知道可以去取走商品了,当消费者取走一个商品后,生产者就知道可以继续生产一个商品了,这是同步问题,所谓互斥问题,是指某个共享资源在一次操作中,只能被一个线程或进程占有,其他的线程或进程不能对它进行操作,比如对一个共享内存的读写操作,当一个进程对它写的时候,另一个进程就不能对

多线程(二) 线程的安全隐患

有了多线程就有了资源竞争,当多个线程对同一资源进行操作时就容易出现安全隐患. 下面举一个卖票的例子来说明线程的安全隐患 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @interface ViewController () @property (assign, nonatomic)NSInteger ticke

细说.NET中的多线程 (二 线程池)

上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数量到达了我们设置的上限,线程会自动排队等待,当线程资源可用时,队列中的线程任务会依次执行,如果没有排队等候的资源,线程会变为闲置状态. 使用ThreadPool来访问线程池 这种做法可以让我们不用那么复杂的去实现创建,重用线程的逻辑,但是也有一些限制,比如由他内置的方法,我们不知道什么时候线程池里面

Java多线程 二 线程间通信

线程间通信: 多个线程在处理同一资源,但是 等待唤醒机制 涉及的方法: 1.wait() 让线程处于冻结状态,被wait的线程会被存储到线程池中. 2.notify() 唤醒线程池中的一个线程(任意) 3.notifyAll() 唤醒线程池中的所有线程.. 这些方法都必须定义在同步中, 因为这些方法是用于操作线程状态的方法. 必须明确到底操作的那个锁上的线程. 为什么操作线程的方法wait notify notifyAll定义在了Object中. 因为这些方法是监视器方法,监视器其实就是锁. 锁