使用 pthread_cond_signal 的优雅线程终止证明有问题

Posted

技术标签:

【中文标题】使用 pthread_cond_signal 的优雅线程终止证明有问题【英文标题】:graceful thread termination with pthread_cond_signal proving problematic 【发布时间】:2010-02-23 12:16:49 【问题描述】:

我需要触发一堆线程并希望优雅地关闭它们。

我正在尝试使用pthread_cond_signal/pthread_cond_wait 来实现此目的,但遇到了问题。

这是我的代码。首先是thread_main

static void *thrmain( void * arg )

    // acquire references to the cond var, mutex, finished flag and
    // message queue
    .....

    while( true )
    
        pthread_mutex_lock( &lock );

        if ( msq.empty() )
        
            // no messages so wait for one.
            pthread_cond_wait( &cnd, &lock );
        

        // are we finished.
        if ( finished )
        
            // finished so unlock the mutex and get out of here
            pthread_mutex_unlock( &lock );
            break;
        

        if ( !msg.empty() )
        
            // retrieve msg
            ....

            // finished with lock
            pthread_mutex_unlock( &lock );

            // perform action based on msg
            // outside of lock to avoid deadlock
        
        else
        
            // nothing to do so we're
            // finished with the lock.
            pthread_mutex_unlock( &lock );
        
    

    return 0;

现在,这一切看起来都很好(无论如何对我来说)。

所以为了拆线我有这个方法

void teardown()

    // set the global finished var
    pthread_mutex_lock( &lock );
    finished = true;
    pthread_mutex_unlock( &lock );

    // loop over the threads, signalling them
    for ( int i = 0 ; i < threads.size() ; ++i )
    
        // send a signal per thread to wake it up
        // and get it to check it's finished flag
        pthread_cond_signal( &cnd );
    

    // need to loop over the threads and join them.
    for ( int i = 0 ; i < threads.size() ; ++i )
    
        pthread_join( threads[ i ].tid, NULL );
    

现在我知道pthread_cond_signal 不能保证它会唤醒哪个线程,所以我无法发出信号并加入同一个循环。然而,这就是一切都出错的地方。如果没有线程在等待,pthread_cond_signal 什么都不做,因此可能某些线程不会收到信号,因此不会知道退出。

我该如何解决这个问题。

M.

***** 更新 ***** 请不要发布我应该使用 pthread_cond_broadcast 的帖子,因为这表现出完全相同的行为。它只会唤醒一个实际上正在等待 cond var 的线程。在此期间正在处理并稍后返回等待的任何线程都将错过信号并且将被遗忘


【问题讨论】:

【参考方案1】:

首先,你必须改变你的谓词

if ( msq.empty() ) 
  // no messages so wait for one.
  pthread_cond_wait( &cnd, &lock );

while ( msq.empty() ) 
  // no messages so wait for one.
  pthread_cond_wait( &cnd, &lock );

这是一个 pthreads 的事情,你必须防范spurious wakeups。

现在你可以把它改成

while ( msq.empty()  && !finished) 
  // no messages so wait for one.
  pthread_cond_wait( &cnd, &lock );

在检查之后,您已经测试是否已设置完成并退出,如果是, 您所要做的就是向所有线程发出信号。

因此,在您的拆解函数中,将循环替换为:

pthread_cond_broadcast(&cond);

这应该确保所有线程都被唤醒,并将看到finished 设置为true 并退出。

即使您的线程没有卡在pthread_cond_wait 中,这也是安全的。如果线程正在处理消息,它们将不会收到唤醒信号,但是它们会完成该处理,再次进入循环并看到 finished == false 并退出。

另一个常见的模式是注入毒消息。有害消息只是您的线程可以识别的表示“停止”的特殊消息,您可以将尽可能多的这些消息放入队列中。

【讨论】:

没有。这只是对先前相同答案的更优雅的重复。 pthread_cond_broadast 只会向实际在 cond var 上等待的线程发出信号。如果任何线程正忙于处理,那么当最终到达等待调用时,它们将不会唤醒。 如果他们正忙于处理,他们已经被唤醒,他们将退出下一个循环,因为完成为真。因为你的 teardown() 在你设置完成时持有互斥锁,所以你很安全。 如前所述,当您发出信号时,线程不需要处于等待状态。 如果你使用有毒信息方法,你根本不需要完成标志。 你应该保持锁定直到你完成信号,我友好的手册页上写着Unlocking the mutex and suspending on the condition variable is done atomically. Thus, if all threads always acquire the mutex before signaling the condition, this guarantees that the condition cannot be signaled (and thus ignored) between the time a thread locks the mutex and the time it waits on the condition variable.参见这里参考manpage.b0red.de/3thr+pthread_cond_signal【参考方案2】:

我猜你应该在调用 pthread_cond_signal 之后解锁互斥锁。另外,请在获取互斥锁后进入条件等待之前检查“完成”的条件。希望这会有所帮助!

【讨论】:

【参考方案3】:

您想使用pthread_cond_broadcast() 而不是pthread_cond_signal()。前者解除阻塞所有在给定条件下等待的线程。

【讨论】:

如果线程在你调用后进入等待还是会失败。 这就是重点。只有在 cond var 上有线程阻塞时它才会执行任何操作。如果没有,那么这个线程将不知道退出..这是问题的症结, @Tronic:原始发布者应该仍然使用pthread_cond_broadcast(),而不是pthread_cond_signal() 循环。我的回答不完整(对于手头的问题,需要在等待之前而不是之后检查finished),但没有错。 代码有点tl;dr,但你可能是对的。在更复杂的情况下,使用 cond_broadcast 会变得复杂,取消可能会更好。【参考方案4】:

我从未直接使用过 pthreads(我更喜欢 Boost.Threads),但我认为您应该调用 pthread_cancel 而不是 pthread_cond_signal

【讨论】:

没有。我特别不想调用 pthread_cancel 这就是为什么我试图实现一个很好的优雅关闭。取消一个线程充满了比我试图解决的问题更严重的问题。 您可以将 pthreads 设置为仅在取消点取消,这样可以优雅地退出。不过,感谢-1 :( 即使只在取消点取消也是危险的;在许多情况下,它会导致资源泄漏。 您能说得更具体些吗?当然,函数中的 return 语句可能会导致资源泄漏,但考虑到要注意正确处理,我不明白为什么取消会成为问题。

以上是关于使用 pthread_cond_signal 的优雅线程终止证明有问题的主要内容,如果未能解决你的问题,请参考以下文章

pthread_cond_signal() 没有给信号线程足够的时间运行

Linux:pthread_cond_signal() 在 Signal Handler() 中不起作用

设置缓冲区和调用pthread_cond_signal时是否需要设置mutexlock / unlock

如果 pthread_cond_signal 是线程中的最后一次调用,是不是存在数据竞争?

条件变脸pthread_cond_signal丢失问题

perfreduce pthread_cond_signal via wait counter