【问题标题】:Implementation of condition variables条件变量的实现
【发布时间】:2012-06-15 13:48:25
【问题描述】:

为了理解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));
}

【问题讨论】:

  • 您正在释放 self 节点而不将其从列表中删除。
  • @n.m. self 节点被signalbroadcast 移除。
  • @JensGustedt 是的,我的错
  • 我意识到这纯粹是教育性的,但我想我应该提到信号量不需要互斥锁来保证信号不会丢失。如果你 sem_post 没有人在听,sem_wait 仍然会在你下次打电话时接听它。信号量基本上是原子计数器,用于阻止以防止变为负数。
  • 我认为my_pthread_cond_signal 和`my_pthread_cond_broadcast´ 有相同的实现,对吗?

标签: c linux multithreading gcc pthreads


【解决方案1】:

基本上你的策略看起来不错,但你有一个主要危险、一些未定义的行为和一个挑剔的选择:

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

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

【讨论】:

  • 为什么不强制返回 malloc/calloc?
  • 首先,最重要的是,它没用。将void* 分配给任何指针都是有效的,这就是它在C 中的用途。其次,只有在绝对必要时才使用强制类型转换。它们很难以文本形式找到并关闭所有警告和诊断。第三,这可以隐藏一个微妙的错误,当您忘记 #include 并且编译器将其返回 int 时。
【解决方案2】:

你似乎不尊重这个要求:

这些函数以原子方式释放互斥体并导致调用线程阻塞条件变量 cond; atomically 这里的意思是 “原子地相对于另一个线程访问互斥锁,然后访问条件变量”。也就是说,如果另一个线程能够获取互斥锁 在即将阻塞的线程释放它之后,随后调用该线程中的 pthread_cond_broadcast() 或 pthread_cond_signal() 表现得好像它是在即将阻塞的线程被阻塞后发出的一样。

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

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

【讨论】:

  • 我不认为这是一个问题。信号量已正确初始化。因此,即使另一个线程启动,信号量也会存储任何将由signalbroadcast 标记为post 的令牌。这样的post 操作可以在线程实际调用sem_wait 之前发生,或者在它已经存在时发生。在这两种情况下,线程都会继续执行。
【解决方案3】:

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

  • sem_destroy 未被调用。
  • 信号/广播在唤醒目标线程后触摸cond_node_t,可能导致use-after-free。

更多内容:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-30
    • 2020-09-07
    • 2010-11-16
    • 2017-05-13
    • 2011-07-18
    • 2012-10-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多