pthread_cond_destroy死锁卡住问题处理记录

本文探讨了在销毁条件变量过程中遇到的问题,特别是当其他线程正在等待时的不确定性行为。通过源码分析揭示了问题根源在于条件变量在未初始化状态下就被使用的错误做法,并给出了正确的解决方案。

问题

供应商代码, 在退出某线程时, 销毁条件变量的过程中, 线程被阻塞.

在这里插入图片描述

参考手册

参看man手册, 销毁其它线程正在等待的cond将导致不确定行为:

pthread_cond_destroy()
It  shall be safe to destroy an initialized condition variable upon which no threads are currently blocked. Attempting to destroy a condition variable upon which other threads are currently blocked results in undefined behavior.

因此在销毁之前, 先发送pthread_cond_broadcast(&pEvent->cond);
通知所有等待线程:

int32_t osEventDestroy(osEvent *pEvent)
{
    pthread_mutex_lock(&pEvent->mutex);
    pthread_cond_broadcast(&pEvent->cond);
    pthread_mutex_unlock(&pEvent->mutex);

    pthread_cond_destroy(&pEvent->cond);
    pthread_mutex_destroy(&pEvent->mutex); 

再次测试, 问题还是有概率出现.

查看源码

查看源码描述, __pthread_cond_destroy 默认有其它线程在等待, 因此将会等待__wrefs变量的值:


/* See __pthread_cond_wait for a high-level description of the algorithm.

   A correct program must make sure that no waiters are blocked on the condvar
   when it is destroyed, and that there are no concurrent signals or
   broadcasts.  To wake waiters reliably, the program must signal or
   broadcast while holding the mutex or after having held the mutex.  It must
   also ensure that no signal or broadcast are still pending to unblock
   waiters; IOW, because waiters can wake up spuriously, the program must
   effectively ensure that destruction happens after the execution of those
   signal or broadcast calls.
   Thus, we can assume that all waiters that are still accessing the condvar
   have been woken.  We wait until they have confirmed to have woken up by
   decrementing __wrefs.  */
int
__pthread_cond_destroy (pthread_cond_t *cond)
{
  LIBC_PROBE (cond_destroy, 1, cond);

  /* Set the wake request flag.  We could also spin, but destruction that is
     concurrent with still-active waiters is probably neither common nor
     performance critical.  Acquire MO to synchronize with waiters confirming
     that they finished.  */
  unsigned int wrefs = atomic_fetch_or_acquire (&cond->__data.__wrefs, 4);
  int private = __condvar_get_private (wrefs);
  while (wrefs >> 3 != 0)
    {
      futex_wait_simple (&cond->__data.__wrefs, wrefs, private);
      /* See above.  */
      wrefs = atomic_load_acquire (&cond->__data.__wrefs);
    }
  /* The memory the condvar occupies can now be reused.  */
  return 0;
}

打印销毁之前__wrefs的为-8, 不可理喻. 尝试将其强制清零之后, 问题消失.

    //
int32_t osEventDestroy(osEvent *pEvent)
{
    pthread_mutex_lock(&pEvent->mutex);
    pthread_cond_broadcast(&pEvent->cond);
    pthread_mutex_unlock(&pEvent->mutex);
	if(0 != pEvent->cond.__data.__wrefs)
	{
		OSLAYER_ERR("%p %s cond error with refs %d\n",pEvent,__func__,pEvent->cond.__data.__wrefs);
		pEvent->cond.__data.__wrefs = 0;
	}
    pthread_cond_destroy(&pEvent->cond);
    pthread_mutex_destroy(&pEvent->mutex); 

追查原因

查阅代码, 没有其它更多的线程在使用该变量, 那么为啥该值会异常呢?
最后发现, 是因为源码在使用条件变量时, 先启动了等待线程pthread_cond_wait, 再进行了cond的初始化.
也就是说,pthread_cond_wait带入了条件变量的时候, 该条件变量并没有初始化, 执行完成了pthread_cond_wait之后, 才调用了pthread_cond_init初始化变量.

调整代码逻辑之后, 问题消失.

结论:

使用未初始化的条件变量, 函数不会报错,但可能产生执行异常.

实验二 线程同步 一、实验目的 1.加深对并发执行、共享资源访问等概念的理解; 2.初步掌握多线程同步技术,如互斥锁、信号量、条件变量等。 二、实验题 生产者-消费者模型是多线程编程中一个经典的同步问题。在这个模型中,生产者线程负责生产产品并将其放入缓冲区,消费者线程负责从缓冲区中取出产品并消费。为了确保线程安全,需要使用互斥锁和条件变量来同步生产者和消费者的操作。 用C/C++模拟实现一个生产者—消费者模型。 要求:设计一个循环缓冲区用于存放生产者生产的产品(用一个整数表示产品,缓冲区用数组表示),设计两个线程,一个用于生产产品并放入缓冲区,一个从缓冲区中取出产品消费。注意对缓冲区互斥访问,使用C或C++中有关线程同步的函数实现。 提示: 可以使用互斥锁访问缓冲区。 1.先创建一个互斥锁的对象。互斥锁的类型是pthread_mutex_t,所以定义一个变量 pthread_mutex_t mutex; 就创建了一个互斥锁。 如果想使用这个互斥锁,我们还需要对这个互斥锁进行初始化,使用函数 pthread_mutex_init() 对互斥锁进行初始化操作。 方法为:pthread_mutex_init(&mutex, NULL); 2.使用互斥锁进行互斥操作。 在进行互斥操作的时候,应该先"拿到锁"再执行需要互斥的操作,否则可能会导致多个线程都需要访问的数据结果不一致。例如在一个线程在试图修改一个变量的时候,另一个线程也试图去修改这个变量,那就很可能让后修改的这个线程把前面线程所做的修改覆盖了。 (1)阻塞调用 pthread_mutex_lock(&mutex); 这个操作是阻塞调用。如果这个锁此时正在被其它线程占用,那么 pthread_mutex_lock() 调用会进入到这个锁的排队队列中,并会进入阻塞状态, 直到拿到锁之后才会返回。 (2)释放互斥锁。用完了互斥锁,一定要记得释放,不然下一个想要获得这个锁的线程,就会一直等待。使用 pthread_mutex_unlock() 即可释放互斥锁。 pthread_mutex_unlock(&mutex); 用条件变量判断缓冲区空或者满。 pthread库中用于线程同步的一种机制。在多线程程序中,条件变量通常与互斥锁(pthread_mutex_t)一起使用,以防止并发问题,如竞态条件和死锁。 (1)条件变量(pthread_cond_t)操作函数pthread_cond_init() 初始化一个条件变量。 int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) 如: pthread_cond_t not_full; pthread_cond_init(&not_full, NULL); (2) pthread_cond_wait 在等待条件变量时,阻塞线程并释放互斥锁。当被pthread_cond_signal或pthread_cond_broadcast唤醒时,线程重新获得锁,并继续执行。 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); (3)pthread_cond_signal 唤醒至少一个等待给定条件变量的线程。 int pthread_cond_signal(pthread_cond_t *cond); (4) pthread_cond_destroy 销毁条件变量。 int pthread_cond_destroy(pthread_cond_t *cond); 对实验进行总结200字
最新发布
10-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值