设为首页 加入收藏

TOP

6.7.1 终止线程(2)
2013-10-07 12:58:50 来源: 作者: 【 】 浏览:59
Tags:6.7.1 终止 线程

6.7.1  终止线程(2)

在示例6-3中,取消被设置为立即发生。这意味着取消线程的请求可以在线程的函数执行中的任何一点发生。这样,线程可以打开文件并在对文件进行写入时被取消。

取消对等线程不应当轻易进行。有些线程具有非常敏感的性质,可能会要求安全保护以防止不合时宜的取消。在线程的函数中安装安全保护能够防止不期望的状况发生。例如,考虑共享数据的线程。根据使用的线程模型,一个线程可能正在处理数据,这些数据要传递给另一个线程进行处理。当线程处理数据时,它通过对互斥量的加锁获得对数据的独自占有。如果线程在互斥量释放之前被取消,这将会导致死锁。在数据能够再次被使用之前,可能会要求它处于某种状态。如果线程在完成这些之前被取消,会发生不期望的状况。根据线程正在进行的处理的类型,线程取消只应当在安全的时候进行。

重要的线程可能会完全防止取消。因此,线程取消应当被限制在不重要的线程上,而且是没有对资源加锁或者没有在执行重要代码的执行点上。您应当将线程的可取消性设置为适当的状态和类型。取消应当被延迟,直到所有重要的清理都发生了,例如释放互斥量、关闭文件等。如果线程有着取消清理处理程序任务,它们应当在取消之前进行。当返回最后的处理程序后,调用线程特有数据的析构器,然后线程被终止。

使用取消点

当推迟取消请求时,线程的终止会推迟到线程的函数执行的后期。当取消发生时,应当是安全的,因为它不处于对互斥量加锁、执行关键代码、令数据处于某种不可用状态的情况中。代码的执行中,这些安全的位置是很好的取消点位置。取消点是一个检查点,线程在这里检查是否有任何取消请求未决,如果有,则终止。

取消点通过调用pthread_testcancel( )来标记。这个函数用于检查任意的未决取消请求。如果有未决的请求,它会导致取消过程发生在这个函数被调用的位置。如果没有未决的取消请求,则函数继续执行,调用不产生任何影响。这个函数调用应当放置在代码中认为可以安全终止进程的任意位置。

调用形式

  1. #include <pthread.h> 
  2.  
  3. void pthread_testcancel(void); 

在示例6-3中,线程的可取消性被设置为立即可取消。示例6-4使用延迟取消,这是默认的设置。对pthread_testcancel( )的调用标记了在哪里取消这个线程是安全的,即在文件被打开之前,或线程关闭文件之后。

示例6-4

  1. // Example 6-4 task1 thread sets its cancelability state to be deferred.  
  2.  
  3.  
  4. void *task1(void *X)  
  5. {  
  6.    int OldState,OldType;  
  7.  
  8.    //not needed default settings for cancelability  
  9.    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&OldState);  
  10.    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&OldType);  
  11.  
  12.  pthread_testcancel();  
  13.  
  14.    ofstream Outfile("out1.txt");  
  15.    for(int Count = 1;Count < 1000;Count++)  
  16.    {  
  17.       Outfile << "thread 1 is working: " << Count << endl;  
  18.  
  19.    }  
  20.    Outfile.close();  
  21.  pthread_testcancel();return (NULL);  

在示例6-5中,我们创建了两个线程并将它们取消。

示例6-5

  1. //Example 6-5 shows two threads being canceled.  
  2.  
  3.  //...  
  4. int main(int argc, char *argv[])  
  5. {  
  6.    pthread_t Threads[2];  
  7.    void *Status;  
  8.  
  9.    pthread_create(&(Threads[0]),NULL,task1,NULL);  
  10.    pthread_create(&(Threads[1]),NULL,task3,NULL);  
  11.  
  12.  
  13.     // ...  
  14.  
  15.    pthread_cancel(Threads[0]);  
  16.    pthread_cancel(Threads[1]);  
  17.  
  18.  
  19.    for(int Count = 0;Count < 2;Count++)  
  20.    {  
  21.       pthread_join(Threads[Count],&Status);  
  22.       if(Status == PTHREAD_CANCELED){  
  23.          cout << "thread" << Count << " has been canceled" << endl;  
  24.       }  
  25.       else{  
  26.               cout << "thread" << Count << " has survived" << endl;  
  27.       }  
  28.    }  
  29.    return (0);  

在示例6-5中,主线程创建两个线程,然后对每个线程发出了取消请求。主线程为每个线程调用了pthread_join( )。函数pthread_join( )如果试图同已经被终止的线程进行结合,则不会失败。结合函数提取被终止线程的退出状态。这种方式较好,因为发出取消请求的线程可能不是调用pthread_join( )的线程。监视所有worker线程的工作情况可能是某个线程的主要任务,该线程也负责取消线程。另一个线程可能会通过调用pthread_join( )函数来检测线程们的退出状态。这种信息可用于静态评估哪个线程的性能最好。在本例中,主线程在循环中结合并检查每个线程的退出状态。被取消的线程会返回退出状态PTHREAD_CANCELED。

利用可安全取消的库函数和系统调用

在这些例子中,通过调用pthread_testcancel( )标记的取消点放置在用户定义的函数中。当您从使用异步取消的线程函数中调用库函数时,取消这些线程是安全的吗?

pthread库定义了可作为取消点的函数以及认为可异步安全取消的函数。这些函数阻塞调用线程,当调用线程被阻塞时,取消线程是安全的。这些是用作取消点的pthread库函数:

  1. pthread_testcancel( )  
  2.  
  3. pthread_cond_wait( )  
  4.  
  5. pthread_timedwait( )  
  6.  
  7. pthread_join( ) 

如果状态为延迟取消的线程在做出对这些pthread库函数的调用时,有挂起的取消请求,则开始进行取消过程。

表6-6列出了一些被要求为取消点的POSIX系统调用。这些pthread和POSIX函数可以安全地用作延迟取消点,但是可能对于异步取消是不安全的。如果不是可安全异步取消的库调用在执行期间被取消,则可能导致库数据处于矛盾的状态。库可能已经为线程分配了内存,当线程被取消时,可能仍占有该内存。在这种情况下,当从不是可异步取消的线程进行这样的库调用时,有必要在调用之前改变可取消状态,并在函数返回后将可取消状态改变回来。

表6-6

POSIX系统调用(取消点)< xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

accept( )

nanosleep( )

sem_wait( )

aio_suspend( )

open( )

send( )

clock_nanosleep( )

pause( )

sendmsg( )

close( )

poll( )

sendto( )

connect( )

pread( )

sigpause( )

create( )

pthread_

cond_timedwait( )

sigsuspend( )

fcntl( )

pthread_cond_wait( )

sigtimedwait( )

fsync( )

pthread_join( )

sigwait( )

getmsg( )

putmsg( )

sigwaitinfo( )

lockf( )

putpmsg( )

sleep( )

mq_receive( )

pwrite( )

system( )

mq_send( )

read( )

usleep( )

mq_timedreceive( )

readv( )

wait( )

mq_timedsend( )

recvfrom( )

waitpid( )

msgrcv( )

recvmsg( )

write( )

msgsnd( )

select( )

writev( )

msync( )

sem_timedwait( )

 


对于其他不是可安全取消(异步或延迟)的库函数和系统函数,可能需要书写代码,通过禁止取消或将取消延迟到函数已经返回来防止线程终止。

示例6-6是对库调用或系统调用的封装。通过封装将可取消性改为延迟的、进行函数或系统调用,然后将可取消性重设回之前的类型。现在就可以安全地调用pthread_testcancel( )了。

示例6-6

  1. //Example 6-6 shows a wrapper for system functions.  
  2.  
  3. int OldType;  
  4. pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&OldType);  
  5. system_call(); //some library of system call  
  6. pthread_setcanceltype(OldType,NULL);  
  7. pthread_testcancel();  
  8.  
  9. //... 

终止之前进行清理

我们在前面提到过,线程在终止之前,可能需要执行一些最终的处理,例如关闭文件、将共享资源重设为一致的状态、释放锁和释放资源。pthread库定义了一种机制,来为每个线程在终止之前执行最后的任务。清理栈(cleanup stack)同每个线程关联,在清理栈中包含了指向取消过程中要执行的例程的指针。函数pthread_cleanup_push( )将一个指向例程的指针压入清理栈中。

调用形式

  1. #include <pthread.h> 
  2.  
  3. void pthread_cleanup_push(void (*routine)(void *), void *arg);  
  4. void pthread_cleanup_pop(int execute); 

参数routine是要被压入栈中的函数指针,参数arg被传递给该函数。当线程在这些环境下退出时,会以arg为参数调用函数routine:

当调用pthread_exit( )时

当线程接受终止请求时

当线程使用非零execute值显式调用pthread_cleanup_pop( )时

该函数不进行返回。

函数pthread_cleanup_pop( )从调用线程的清理栈的顶部删除routine的指针。参数execute的值可以为1或0。如果为1,线程执行routine,即使它没有被终止。线程从调用该函数之后的位置继续执行。如果值为0,则指针会从栈顶移除,指向的函数不会被执行。

对于每次入栈,需要在相同的词法范围(lexical scope)中存在出栈。例如,task4( )要求函数退出或被取消时执行清理处理程序。

在示例6-7中,task4( )通过调用pthread_cleanup_push( )函数将清理处理程序cleanup_task4( )压入栈中。对于每个对pthread_cleanup_push( )函数的调用,都要求有相应的pthread_cleanup_pop( )。pop函数被传递参数0,意味着此时处理程序已经从清理栈中移出,但是此时尚未被执行。如果取消了执行task4( )的线程,则会执行该处理程序。

示例6-7

  1. //Example 6-7 task4 () pushes cleanup handler 
    cleanup_task4 () onto cleanup stack.  
  2.  
  3. void *task4(void *X)  
  4. {  
  5.    int *Tid;  
  6.    Tid = new int;  
  7.    // do some work  
  8.    //...  
  9.    pthread_cleanup_push(cleanup_task4,Tid);  
  10.    // do some more work  
  11.    //...  
  12.    pthread_cleanup_pop(0);  

在示例6-8中,task5( )将清理处理程序cleanup_task5( )压入到清理栈中。这个例子同上一个例子的区别在于传递给pthread_cleanup_pop( )的参数为1,意味着处理程序从清理栈中移出,并在这个位置被执行。无论执行task5( )的线程是否被取消,处理程序都会被执行。清理处理程序cleanup_task4( )和cleanup_task5( )都是正规的函数,可用于关闭文件、释放资源、解锁互斥量等。

示例6-8

  1. //Example 6-8 task5 () pushes cleanup handler
    cleanup_task5 () onto cleanup stack.  
  2.  
  3. void *task5(void *X)  
  4. {  
  5.    int *Tid;  
  6.    Tid = new int;  
  7.    // do some work  
  8.    //...  
  9.    pthread_cleanup_push(cleanup_task5,Tid);  
  10.    // do some more work  
  11.    //...  
  12.    pthread_cleanup_pop(1);  

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇6.4.4 调度分配域 下一篇6.7.1 终止线程(1)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: