条件变量的实现

pyt*_*nic 7 c linux multithreading gcc pthreads

为了理解pthread条件变量的代码,我编写了自己的版本.它看起来是否正确?我在一个程序中使用它,它的工作,但工作速度惊人得多.最初程序大约需要2.5秒,使用我的条件变量版本只需0.8秒,程序输出也是正确的.但是,我不确定,如果我的实施是正确的.

struct cond_node_t
{
    sem_t s;
    cond_node_t * next;
};

struct cond_t
{
    cond_node_t * q;                // Linked List
    pthread_mutex_t qm;                 // Lock for the Linked List
};

int my_pthread_cond_init( cond_t * cond )
{
    cond->q = NULL;
    pthread_mutex_init( &(cond->qm), NULL );
}

int my_pthread_cond_wait( cond_t* cond, pthread_mutex_t* mutex )
{
    cond_node_t * self;

    pthread_mutex_lock(&(cond->qm));
    self = (cond_node_t*)calloc( 1, sizeof(cond_node_t) );
    self->next = cond->q;
    cond->q = self;
    sem_init( &self->s, 0, 0 );
    pthread_mutex_unlock(&(cond->qm));

    pthread_mutex_unlock(mutex);
    sem_wait( &self->s );
    free( self ); // Free the node
    pthread_mutex_lock(mutex);
}

int my_pthread_cond_signal( cond_t * cond )
{
    pthread_mutex_lock(&(cond->qm));
    if (cond->q != NULL) 
    {
        sem_post(&(cond->q->s));
        cond->q = cond->q->next;
    }
    pthread_mutex_unlock(&(cond->qm));
}

int my_pthread_cond_broadcast( cond_t * cond )
{
    pthread_mutex_lock(&(cond->qm));
    while ( cond->q != NULL) 
    {
        sem_post( &(cond->q->s) );
        cond->q = cond->q->next;
    }
    pthread_mutex_unlock(&(cond->qm));
}
Run Code Online (Sandbox Code Playgroud)

n. *_* m. 5

您似乎不尊重此要求:

这些函数自动释放互斥体并导致调用线程在条件变量 cond 上阻塞;原子地在这里意味着“原子地相对于另一个线程访问互斥体然后访问条件变量”。也就是说,如果另一个线程能够在即将阻塞的线程释放互斥锁后获取该互斥锁,则该线程中对 pthread_cond_broadcast() 或 pthread_cond_signal() 的后续调用的行为应如同在即将阻塞的线程之后发出一样。 to-block 线程已被阻塞。

您解锁然后等待。另一个线程可以在这些操作之间做很多事情。

PS我不确定自己是否正确解释了这一段,请随时指出我的错误。

  • 我不认为这是一个问题。信号量已正确初始化。因此,即使另一个线程启动,信号量也会存储将由“信号”或“广播”“发布”的任何令牌。这样的“post”操作可以在线程实际调用“sem_wait”之前或已经进入时发生。在这两种情况下,线程都会继续执行。 (2认同)

jil*_*les 5

除了缺少返回值检查之外,还有一些应该可以修复的问题:

  • sem_destroy 不叫。
  • 信号/广播cond_node_t在唤醒目标线程后接触,可能导致释放后使用。

进一步评论:

  • 省略的销毁操作可能需要对其他操作进行更改,因此当 POSIX 说它应该是安全的时,销毁条件变量是安全的。不支持 destroy 或对何时调用它施加更强的限制将简化事情。
  • 生产实现将处理线程取消。
  • 退出等待(例如线程取消和pthread_cond_timedwait超时所需)可能会导致复杂化。
  • 您的实现在用户空间中对线程进行排队,出于性能原因,在某些生产实现中会这样做;我不明白为什么。
  • 您的实现始终按 LIFO 顺序对线程进行排队。这通常更快(例如由于缓存效应),但可能会导致饥饿。生产实现有时可能会使用 FIFO 顺序来避免饥饿。


Jen*_*edt 4

基本上你的策略看起来不错,但是你有一个主要危险,一些未定义的行为,以及一个挑剔的问题:

  • 您没有检查 POSIX 函数的返回值。特别sem_wait是它是可中断的,因此在重负载或运气不好的情况下,您的线程将被虚假唤醒。你必须仔细捕捉所有这些
  • 您的函数都没有返回值。如果函数的某些用户有一天决定使用返回值,则这是未定义的行为。仔细分析条件函数允许返回的错误代码并执行此操作。
  • 不要投射mallocor的返回值calloc

编辑:实际上,你根本不需要malloc/ free。局部变量也可以。

  • 首先,最重要的是,它没有用。将“void*”分配给任何指针都是有效的,这就是它在 C 中的用途。其次,仅在绝对必要时才使用强制转换。它们很难通过文字找到,并且会关闭所有警告和诊断。第三,当您忘记“#include”并且编译器将其返回“int”时,这可能会隐藏一个微妙的错误。 (3认同)