注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

pthread_cancel及pthread_cond_wait  

2011-08-21 22:14:49|  分类: Glibc分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

一、pthread_cancel

这个函数是pthread中取消一个线程最为顺手的一个函数,但是对于一些框架性的代码(就是无法规定和预料在调用的一个约定函数中会执行什么操作,执行多长时间等),如果处理不当,就可能造成资源的泄露。例如,我们在封装的用户函数的前面通过mmap为线程分配了单独的信号处理函数堆栈,所以就需要在函数之后向系统归还这些资源,但是如果没有考虑到pthread_cancel并且是能了异步取消,就可能造成资源泄漏。这对于一个需要在无人值守的情况下运转的嵌入式系统来说是比较危险的一件事情,所以我们需要知道这个操作执行的步骤以及内部实现。

1、线程的取消

相关使用宏的定义

  /* Flags determining processing of cancellation.  */
  int cancelhandling;
  /* Bit set if cancellation is disabled.  */
#define CANCELSTATE_BIT  0
#define CANCELSTATE_BITMASK 0x01
  /* Bit set if asynchronous cancellation mode is selected.  */
#define CANCELTYPE_BIT  1
#define CANCELTYPE_BITMASK 0x02
  /* Bit set if canceling has been initiated.  */
#define CANCELING_BIT  2
#define CANCELING_BITMASK 0x04
  /* Bit set if canceled.  */
#define CANCELED_BIT  3
#define CANCELED_BITMASK 0x08
  /* Bit set if thread is exiting.  */
#define EXITING_BIT  4
#define EXITING_BITMASK  0x10
  /* Bit set if thread terminated and TCB is freed.  */
#define TERMINATED_BIT  5
#define TERMINATED_BITMASK 0x20
  /* Bit set if thread is supposed to change XID.  */
#define SETXID_BIT  6
#define SETXID_BITMASK  0x40
  /* Mask for the rest.  Helps the compiler to optimize.  */
#define CANCEL_RESTMASK  0xffffff80

#define CANCEL_ENABLED_AND_CANCELED(value) \
  (((value) & (CANCELSTATE_BITMASK | CANCELED_BITMASK | EXITING_BITMASK       \
        | CANCEL_RESTMASK | TERMINATED_BITMASK)) == CANCELED_BITMASK)
#define CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS(value) \
  (((value) & (CANCELSTATE_BITMASK | CANCELTYPE_BITMASK | CANCELED_BITMASK    \
        | EXITING_BITMASK | CANCEL_RESTMASK | TERMINATED_BITMASK))     \
   == (CANCELTYPE_BITMASK | CANCELED_BITMASK))

2、pthread_cancel的实现

int
pthread_cancel (th)
     pthread_t th;
{
  volatile struct pthread *pd = (volatile struct pthread *) th;

  /* Make sure the descriptor is valid.  */
  if (INVALID_TD_P (pd))
    /* Not a valid thread handle.  */
    return ESRCH;

#ifdef SHARED
  pthread_cancel_init ();
#endif
  int result = 0;
  int oldval;
  int newval;
  do
    {
      oldval = pd->cancelhandling;
      newval = oldval | CANCELING_BITMASK | CANCELED_BITMASK;这里不管三七二十一,首先设置线程为正在取消,并且已经取消

      /* Avoid doing unnecessary work.  The atomic operation can
  potentially be expensive if the bug has to be locked and
  remote cache lines have to be invalidated.  */
      if (oldval == newval)
 break;这一步也比较有意义。也就是当线程的这两个标志位已经被置位的时候(其它的在这里不用关心),之后所有的操作都可以不执行了

      /* If the cancellation is handled asynchronously just send a
  signal.  We avoid this if possible since it's more
  expensive.  */
      if (CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS (newval))从上面的宏可以看到,这里的判断是表示如果新的值是已经被取消了并且是异步取消是使能的,但是另一方面,这个线程的EXITING_BITMASK | CANCEL_RESTMASK | TERMINATED_BITMASK三个不能被置位
 {
   /* Mark the cancellation as "in progress".  */
   atomic_bit_set (&pd->cancelhandling, CANCELING_BIT);

   /* The cancellation handler will take care of marking the
      thread as canceled.  */
   INTERNAL_SYSCALL_DECL (err);

   /* One comment: The PID field in the TCB can temporarily be
      changed (in fork).  But this must not affect this code
      here.  Since this function would have to be called while
      the thread is executing fork, it would have to happen in
      a signal handler.  But this is no allowed, pthread_cancel
      is not guaranteed to be async-safe.  */
   int val;
#if __ASSUME_TGKILL
   val = INTERNAL_SYSCALL (tgkill, err, 3,
      THREAD_GETMEM (THREAD_SELF, pid), pd->tid,
      SIGCANCEL);
#else
# ifdef __NR_tgkill
   val = INTERNAL_SYSCALL (tgkill, err, 3,
      THREAD_GETMEM (THREAD_SELF, pid), pd->tid,
      SIGCANCEL);
   if (INTERNAL_SYSCALL_ERROR_P (val, err)
       && INTERNAL_SYSCALL_ERRNO (val, err) == ENOSYS)
# endif
     val = INTERNAL_SYSCALL (tkill, err, 2, pd->tid, SIGCANCEL);
#endif

   if (INTERNAL_SYSCALL_ERROR_P (val, err))
     result = INTERNAL_SYSCALL_ERRNO (val, err);

   break;
 }
    }
  /* Mark the thread as canceled.  This has to be done
     atomically since other bits could be modified as well.  */
  while (atomic_compare_and_exchange_bool_acq (&pd->cancelhandling, newval,
            oldval
));这一步操作也非常重要,这就标志着无论对方线程处于什么状态(线程取消是否被使能、是延迟取消或者是一部取消),只要执行这个函数,它的标志位始终是被置位的,从之后的代码将会看到,这个对pthread_setcanceltype有影响

  return result;
}

当一个线程进入一个SIGCANCEL的处理函数时,会首先置位这个CANCELING标志,从而这个里面的信号并不会真正发送

sigcancel_handler (int sig, siginfo_t *si, void *ctx)
{
#ifdef __ASSUME_CORRECT_SI_PID
  /* Determine the process ID.  It might be negative if the thread is
     in the middle of a fork() call.  */
  pid_t pid = THREAD_GETMEM (THREAD_SELF, pid);
  if (__builtin_expect (pid < 0, 0))
    pid = -pid;
#endif

  /* Safety check.  It would be possible to call this function for
     other signals and send a signal from another process.  This is not
     correct and might even be a security problem.  Try to catch as
     many incorrect invocations as possible.  */
  if (sig != SIGCANCEL
#ifdef __ASSUME_CORRECT_SI_PID
      /* Kernels before 2.5.75 stored the thread ID and not the process
  ID in si_pid so we skip this test.  */
      || si->si_pid != pid
#endif
      || si->si_code != SI_TKILL)
    return;

  struct pthread *self = THREAD_SELF;

  int oldval = THREAD_GETMEM (self, cancelhandling);
  while (1)
    {
      /* We are canceled now.  When canceled by another thread this flag
  is already set but if the signal is directly send (internally or
  from another process) is has to be done here.  */
      int newval = oldval | CANCELING_BITMASK | CANCELED_BITMASK;

3、线程取消是否使能

int
__pthread_setcancelstate (state, oldstate)
     int state;
     int *oldstate;
{
  volatile struct pthread *self;

  if (state < PTHREAD_CANCEL_ENABLE || state > PTHREAD_CANCEL_DISABLE)
    return EINVAL;

  self = THREAD_SELF;

  int oldval = THREAD_GETMEM (self, cancelhandling);
  while (1)
    {
      int newval = (state == PTHREAD_CANCEL_DISABLE
      ? oldval | CANCELSTATE_BITMASK
      : oldval & ~CANCELSTATE_BITMASK);

      /* Store the old value.  */
      if (oldstate != NULL)
 *oldstate = ((oldval & CANCELSTATE_BITMASK)
       ? PTHREAD_CANCEL_DISABLE : PTHREAD_CANCEL_ENABLE);

      /* Avoid doing unnecessary work.  The atomic operation can
  potentially be expensive if the memory has to be locked and
  remote cache lines have to be invalidated.  */
      if (oldval == newval)
 break;

      /* Update the cancel handling word.  This has to be done
  atomically since other bits could be modified as well.  */
      int curval = THREAD_ATOMIC_CMPXCHG_VAL (self, cancelhandling, newval,
           oldval);
      if (__builtin_expect (curval == oldval, 1))
 {
   if (CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS (newval))如果取消被使能并且已经设置了取消,这个就是刚才pthread_cancel设置的标志位,其中设置了CANCELING和CANCELED两个标志位,从而会满足这里的CANCELED,如果这里pthread_setcanceltype设置为可以ASYNCHRONOUS,那么就可以马上进行检测了,这说明这个函数是一个pthread_cancel的取消点
     __do_cancel ();

   break;
 }

      /* Prepare for the next round.  */
      oldval = curval;
    }

  return 0;
}

4、取消的类型

取消有两种类型,一种是马上取消,也就通过主动发信号的形式让对方线程通过信号处理函数来对线程透明的执行取消操作,另一种是记账方式,也就是在里面记账说线程已经被取消,然后线程在执行的特殊位置通过预定的检测,也就是轮询的方式,这个轮询的位置就是POSIX中说的取消点,函数名叫pthread_testcancel

int
__pthread_setcanceltype (type, oldtype)
     int type;
     int *oldtype;
{
  volatile struct pthread *self;

  if (type < PTHREAD_CANCEL_DEFERRED || type > PTHREAD_CANCEL_ASYNCHRONOUS)
    return EINVAL;

  self = THREAD_SELF;

  int oldval = THREAD_GETMEM (self, cancelhandling);
  while (1)
    {
      int newval = (type == PTHREAD_CANCEL_ASYNCHRONOUS
      ? oldval | CANCELTYPE_BITMASK
      : oldval & ~CANCELTYPE_BITMASK);

      /* Store the old value.  */
      if (oldtype != NULL)
 *oldtype = ((oldval & CANCELTYPE_BITMASK)
      ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED);

      /* Avoid doing unnecessary work.  The atomic operation can
  potentially be expensive if the memory has to be locked and
  remote cache lines have to be invalidated.  */
      if (oldval == newval)
 break;

      /* Update the cancel handling word.  This has to be done
  atomically since other bits could be modified as well.  */
      int curval = THREAD_ATOMIC_CMPXCHG_VAL (self, cancelhandling, newval,
           oldval);
      if (__builtin_expect (curval == oldval, 1))
 {
   if (CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS (newval))这个设置取消类型的函数同样是一个取消点,要判断线程是否已经被取消,如果是则执行展开操作
     {
       THREAD_SETMEM (self, result, PTHREAD_CANCELED);
       __do_cancel ();
     }

   break;
 }

      /* Prepare for the next round.  */
      oldval = curval;
    }

  return 0;
}
5、取消点的主动检测

void
pthread_testcancel (void)
{
  CANCELLATION_P (THREAD_SELF);
}

/* Cancellation test.  */
#define CANCELLATION_P(self) \
  do {               \
    int cancelhandling = THREAD_GETMEM (self, cancelhandling);        \
    if (CANCEL_ENABLED_AND_CANCELED (cancelhandling))         \这里没有判断取消类型是延迟还是异步,而是只要是能了取消并且已经被取消,那么就可以直接进行展开操作
      {               \
 THREAD_SETMEM (self, result, PTHREAD_CANCELED);         \
 __do_cancel ();             \
      }               \
  } while (0)


二、pthread_cond_wait

这个函数奇怪的地方就是必须自带一个互斥锁,只有在这个锁的配合下才能使用这个函数。这里的原因据说在《UNIX环境高级编程》已经说明,但是我还是不厌其烦的在这里再说一下:

pthread_cond_wait如果不使用前面的互斥锁的话,可能会造成信号的丢失

在一个线程A中使用下面的语句:
if(x == 3)
 pthread_cond_wait
但是如果在if(x==3)不满足之后这个线程被切换另一个线程B执行
x =3
pthread_cond_signal
此时执行if判断的线程就可能丢失信号。因为此时执行pthread_cond_signal的时候线程A还没有开始wait,所以此时的signal将不会有唤醒操作就直接返回,然后A则会继续等待一个已经执行过的唤醒,从而出现信号丢失。如果加上了互斥锁,此时的x的操作就是穿行的,所以不会丢失信号。

pthread_cond_signal的实现看,它并没有试图获得前面pthread_mutex锁,而是只使用了pthread_cond_t中的互斥锁

  评论这张
 
阅读(1632)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017