pthread 相关

pthread 使用


一、thread创建和终止

使用pthread相关函数要包含头文件:pthread.h

#include <pthread.h>

int pthread_create(pthread_t * restrict tidp, const pthread_attr_t * restrict attr, void*(* start_rtn)(void *), void * restrict arg);

restrict关键字用于告诉编译器,pthread_create函数中只会通过指针tidp来访问tidp指向的地址空间,方便优化。详见:https://blog.csdn.net/ly0303521/article/details/48178807

thread终止有三种方式:

1.线程可以简单返回方式退出,返回值是线程的退出码;

2.线程可以调用pthread_exit()函数退出;

3.线程可以被同一进程中的其他线程取消。

void pthread_exit(void * rval_ptr);
int pthread_join(pthread_t thread, void ** rval_ptr);

pthread_exit(void *rval_ptr)中rval_ptr的值可以被pthread_join()函数读取,并传递到*rval_ptr中;

简单的方式返回,返回值一样被pthread_join()函数接收;

被其他线程取消,则pthread_join()函数接收到的值将是PTHREAD_CANCELED,并设置rval_ptr指向的内存单元。取消其他线程的格式可以写成如下格式:

int kill_rc = pthread_kill(threadId,0);

if(kill_rc == ESRCH) {
    //printf("the specified thread did not exists or already quit\n");
}
else {
    //printf("the specified thread is alive,kill it\n");
    pthread_cancel(threadId); 
    pthread_join(threadId, NULL);//wait thread return
    //printf("old thread killed\n");
}

若不能确定要终止的线程是否还存在,可以先调用pthread_kill()发送信号0,用来确定threadId对应的线程是否还存在,若已经终止了,则返回值kill_rc的值将等于ESRCH,若没有终止,调用pthread_cancel()来终止threadId所对应的线程,pthread_join()函数用来等待终止完成。pthread_cancel()调用并不等待线程终止,它只提出请求,目标线程接收到pthread_cancel()的终止请求后有两种方式,a.延时终止即运行到某个取消点;b.立即终止。下文详细介绍终止方式对清除函数pthread_cleanup_push pthread_cleanup_pop的重要性。

二、线程的同步和资源互斥

一般的用法是使用pthread_mutex_t类型的互斥锁来保证资源互斥,使用pthread_cond_t类型的等待条件和pthread_mutex_t类型的互斥锁来实现线程同步(实现指定线程的执行顺序,其实可以用两个信号量模拟,一个信号量用来实现互斥,另一个用于实现等待条件),这里我们不详细讲解,具体使用可以参照一下例程,这里我们主要想讲解的是线程取消如何避免死锁:

    Posix线程终止有两种情况,正常终止和非常正终止,上述讲解的三种线程终止方式第一、二都是正常终止,而第三是非正常终止,非正常终止是在其他线程的干预下,或者自身运行出错而退出的,这种退出方式是不可预见的,不加以处理很容易导致线程的死锁。我们一般的处理方式是添加cleanup函数,作用是当线程被其他线程调用pthread_cancel()退出时,释放占有的资源和锁,避免其他等待使用资源的线程死锁。这里使用参与过的gpsim项目例程说明其用法:

void sim_dma_intf::msg_loop(void)
{
    unsigned int trans_size;
    int n;

    printf("client connect,starting dma msg loop\n");
    pthread_cleanup_push(thread_cleanup, this); // thread cleanup handler    
    while (1)
    {
		
        pthread_mutex_lock(&mutex);
        while (busy==0) {
            pthread_cond_wait(&cond, &mutex);
        }

        /*do something useful*/

        busy = 0;//complete
        pthread_mutex_unlock(&mutex);
    }
    pthread_cleanup_pop(0);
}

类成员函数msg_loop()函数被线程函数thread_func()调用:

void* sim_dma_intf::thread_func(void* arg)
{
    class sim_dma_intf* dma = (class sim_dma_intf*)arg;

    dma->thread = dma->sock->thread_client;
    dma->msg_loop();

    return 0;
}

pthread_cleanup_push()函数中注册的清理函数thread_cleanup()原型如下:

void sim_dma_intf::thread_cleanup(void *arg)
{
    class sim_dma_intf* dma = (class sim_dma_intf*)arg;
    pthread_mutex_unlock(&dma->mutex);
}

用于线程非正常退出前解锁dma->mutex;

以上用到的关键的两个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() 的调用将在清理函数栈中形成一个函数链;

从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到 pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

其实它们是两个宏定义:
#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_push()/pthread_cleanup_pop()函数时,若线程为立即终止,一定要更改终止类型为延迟终止(PTHREAD_CANCEL_DEFERRED,创建线程的缺省状态),如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误;但如果此时线程处于PTHREAD_CANCEL_DEFERRED模式,则线程收到cancel事件也不会退出,而是继续执行直到取消点才能退出,这里的取消点为pthread_cond_wait()函数,我们只需要保证在线程接收到cancel事件后下一个取消点在pthread_mutex_lock()和pthread_mutex_unlock()之间即可,这样才能保证加锁和解锁函数的成对调用,才能不出错。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:

{
    int oldtype;
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
    pthread_cleanup_push(routine, arg);
    ...
    pthread_cleanup_pop(execute);
    pthread_setcanceltype(oldtype, NULL);
 } 


三、取消点 及线程的取消类型相关的属性

1.取消类型

设置取消类型相关的函数如下:

int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);

pthread_setcancelstate()函数设置本线程对cancel事件的反应,state有两种状态:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,接收cancel事件后忽略还是接收,oldstate用来保存老的state;

pthread_setcanceltype()函数用来设置在可以接收cancel事件的前提下,接收到cancel事件后的处理方式,PTHREAD_CANCEL_DEFFERED(缺省)表示线程运行到下一个取消点才能终止当前线程,PTHREAD_CANCEL_ASYCHRONOUS表示立即终止当前线程。

2.取消点分类

取消点在上述两个函数全部设置缺省值时才有意义,pthreads标准指定了几种类型的取消点,其中包括:

a.通过pthread_testcancel()函数调用以编程方式建立的线程取消点;

b.线程等待pthread_cond_wait()或者pthread_cond_timewait()中特定条件;

c.被sigwait(2)阻塞的函数;

d.一些标准的库调用,通常这些调用包括线程可基于阻塞的函数。

缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,
直到再次启用取消请求。  
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标。另外,pthread_mutex_lock(),并不是取消点,也不应该是取消点,不要以是否阻塞判断是否是取消点。
注意:

程序设计方面的考虑,如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用.

3.取消点的实现

下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。

以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:

      /* Enable asynchronous cancellation.  Required by the standard.  */
      cbuffer.oldtype = __pthread_enable_asynccancel ();

      /* Wait until woken by signal or broadcast.  */
      lll_futex_wait (&cond->__data.__futex, futex_val);

      /* Disable asynchronous cancellation.  */
      __pthread_disable_asynccancel (cbuffer.oldtype); 

我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。


参考:

https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html

http://blog.163.com/yangfan_407/blog/static/127950654201012434648971/

https://blog.csdn.net/jasonchen_gbd/article/details/78674716


猜你喜欢

转载自blog.csdn.net/xiaoyink/article/details/80768265