【问题标题】:safe method to wait for all thread timer callbacks completion等待所有线程计时器回调完成的安全方法
【发布时间】:2014-11-11 10:55:04
【问题描述】:

在一次性计时器的情况下,我可以使用信号量来等待计时器回调完成。 但是,如果计时器被多次触发,它就没有帮助。考虑以下代码:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

#define N 10

void timer_threaded_function(sigval_t si)
{
    uint8_t *shared_resource = si.sival_ptr;

    sleep(rand() % 7);

    /* ... manipulate with shared_resource */

    return;
}

int main()
{
    struct sigevent sig_ev = {0};
    uint8_t *shared_resource = malloc(123);
    timer_t timer_id;
    int i;

    sig_ev.sigev_notify = SIGEV_THREAD;
    sig_ev.sigev_value.sival_ptr = shared_resource;
    sig_ev.sigev_notify_function = timer_threaded_function;
    sig_ev.sigev_notify_attributes = NULL;

    timer_create(CLOCK_REALTIME, &sig_ev, &timer_id);

    for (i = 0; i < N; i++) {
        /* arm timer for 1 nanosecond */
        timer_settime(timer_id, 0,
                      &(struct itimerspec){{0,0},{0,1}}, NULL);

        /* sleep a little bit, so timer will be fired */
        usleep(1);
    }

    /* only disarms timer, but timer callbacks still can be running */
    timer_delete(timer_id);

    /* 
     * TODO: safe wait for all callbacks to end, so shared resource
     * can be freed without races.
     */
    ...

    free(shared_resource);

    return 0;
}

timer_delete() 只解除计时器(如果它被武装)并释放与计时器相关的资源。但是计时器回调仍然可以运行。所以我们不能释放shared_resource,否则可能会出现竞争情况。有什么办法可以应对这种情况吗?

我想到了引用计数,但它没有帮助,因为我们不知道实际上有多少线程会尝试访问共享资源(导致计时器溢出)。

【问题讨论】:

  • 您如何确定一定数量的分离线程完成了对共享资源的操作?解决这个问题,你就解决了这个问题。 timer_ API 有点像红鲱鱼 - 分离的线程是由计时器、信号、调用 pthread_create() 的循环创建的,不管怎样。

标签: c linux multithreading timer pthreads


【解决方案1】:

这完全不能令人满意:-(。我已经看过了,似乎没有任何方法可以发现 sigevent (a) 是否尚未被触发,或者 (b) 处于挂起状态,或者 (c) 正在运行,或 (d) 已完成。

我可以建议的最好的方法是额外的间接级别,以及指向共享资源的静态。所以:

  static foo_t* p_shared ;
   ....
  p_shared = shared_resourse ;
   .....
  sig_ev.sigev_value.sival_ptr = &p_shared ;

其中foo_t 是共享资源的类型。

现在我们可以使用一些原子......在timer_threaded_function():

  foo_t** pp_shared ;
  foo_t*  p_locked ;
  foo_t*  p_shared ;

  pp_shared = so.sival_ptr ;
  p_locked  = (void*)UINPTR_MAX ;
  p_shared  = atomic_swap(pp_shared, p_locked) ;

  if (p_shared == p_locked)
    return ;                    // locked already.

  if (p_shared == NULL)
    return ;                    // destroyed already.

  .... proceed to do the usual work ...

  if (atomic_cmp_swap(pp_shared, &p_locked, p_shared))
    return ;                    // was locked and is now restored

  assert(p_locked == NULL) ;

  ... the shared resource needs to be freed ...

在控制线程中:

  timer_delete(timer_id) ;              // no more events, thank you

  p_s = atomic_swap(&p_shared, NULL) ;  // stop processing events

  if (p_s == (void*)UINTPTR_MAX)
    // an event is being processed.

  if (p_s != NULL)
    ... the shared resource needs to be freed ...

当事件线程发现共享资源需要被释放时,它可以自己做,或者通知控制线程事件已经被处理,这样控制线程就可以继续进行释放。这在很大程度上是一个品味问题。

基本上,这是使用原子来提供一种锁,其值为三态:NULL destroy; UINTPTR_MAX 锁定;任何其他解锁。

缺点是static p_shared,它必须一直存在,直到timer_threaded_function() 完成并且永远不会再被调用......因为这些正是不可知的东西,static p_shared 是, 实际上是一个夹具 :-(.

【讨论】:

  • 谢谢,看起来只有额外的外部变量才能固定。它可能是原子 p_shared 指针或某种带有受保护状态标志的互斥锁/自旋锁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多