【问题标题】:possible causes of infinite wait in pthread_cond_waitpthread_cond_wait 中无限等待的可能原因
【发布时间】:2013-12-21 12:22:30
【问题描述】:

我目前正在尝试分析第三方源代码中的一个问题,其中线程(代码 sn-p 对应于THREAD-T1)处于无限等待状态。怀疑是线程卡在pthread_cond_wait。以下是相同的细节。

代码说明

  • T1 对 T2 公开的 API 进行异步调用。
  • 因此 T1 在条件变量(例如 cond_t)上移动到阻塞等待。
  • 条件变量 cond_t 在 T2 生成的回调事件中发出信号。
  • 上述循环重复n 次,直到 API 返回成功。

为了巩固,上面是通过使用条件变量使异步调用类似于同步调用的一系列步骤。

示例代码

   #define MAX_RETRY (3)
   bool            g_b_ret_val;       
   pthread_mutex_t g_cond_mutex;
   pthread_mutex_t g_ret_val_mutex; /* Assume iniitailzed in the main thread */
   pthread_cond_t  g_cond_t; /* Assume iniitailzed in the main thread */

   retry_async_call_routine()  /* Thread-T1 */
   {
       while(( false == g_b_ret_val) && (retry < MAX_RETRY))
       {
           (void)invoke_async_api();
           pthread_mutex_init(&g_cond_mutex, NULL);
           pthread_mutex_lock(&g_cond_mutex);
           pthread_cond_wait(g_cond_t, &g_cond_mutex);
           pthread_mutex_unlock(&g_cond_mutex);
           pthread_mutex_destroy(&g_cond_mutex);
           retry ++ ;
        }
   }

   callback_routine() /* Thread-T2 */
   {
        pthread_mutex_lock(&g_ret_val_mutex);
        g_b_ret_val = true; /* May be false also on failure */
        pthread_mutex_unlock(&g_ret_val_mutex);
        pthread_cond_signal(&g_cond_t);
   }

我在代码中看到的已知问题

  1. pthread_cond_wait 上的 while 循环中缺少对条件的重新测试
  2. 发出信号时缺少互斥锁

问题

  1. 请指出任何更多的循环漏洞(或)无限等待的可能性(如果有)。
  2. g_cond_t 在连续等待之间没有使用pthread_cond_destroy 重置,相同的行为是什么? (有关此的任何参考)

【问题讨论】:

    标签: c multithreading synchronization pthreads


    【解决方案1】:

    这段代码看起来很荒谬。您不应该仅仅为了等待条件变量而创建和销毁互斥锁​​。在使用线程共享数据之前需要创建互斥锁,然后必须使用互斥锁来保护共享数据。在这种情况下,即保护g_b_ret_valg_ret_val_mutex

    条件变量本身仅用于等待(定期或定时等待)和信号(信号或广播)。它通常不需要自己的锁,事实上,有一个单独的锁(如上面的循环)会妨碍调用pthread_cond_wait,它只需要一个互斥锁来解锁,而不是二。除非您需要新的/不同的属性,否则无需销毁和重新创建条件变量。

    “不卡住”的关键——避免无限等待——是要保证,每当一个线程调用pthread_cond_wait时,肯定会有其他线程在将来调用pthread_cond_signal(或@987654326 @)。也就是说,服务员首先测试“为什么要等待”,锁定“为什么”部分,然后仅当“为什么”部分说“你应该等待”时才等待。唤醒线程可以使用 same 锁来确定唤醒是必要的,或者——如果唤醒线程是“惰性的”,如上例中——只需发出一个“叫醒电话”。

    因此,正确性的最小更改似乎是将循环更改为读取:

    pthread_mutex_lock(&g_ret_val_mutex);
    for (retry = 0; retry < MAX_RETRY && !g_b_ret_val; retry++) {
        (void)invoke_async_api();
        pthread_cond_wait(&g_cond_t, &g_ret_val_mutex);
    }
    success = g_b_ret_val; /* if false, we failed */
    /* or: success = retry < MAX_RETRY; -- same result */
    pthread_mutex_unlock(&g_ret_val_mutex);
    

    (顺便说一句:g_cond_t 是一个可怕的变量名称;_t 后缀用于表示类型。)


    有时将“某个线程需要唤醒”与“该线程的最终结果是成功”分开是明智的。如果需要,我可能会使用第二个布尔值添加它。让我们称之为g_waiting,我们设置true,当callback_routine()(假设)被保证被调用并且它应该做一个唤醒事件,false,当它不能保证被调用或唤醒是不需要。这种编码允许您切换到pthread_cond_timedwait,以防异步事件可能由于某种原因永远不会发生。

    鉴于g_ret_val_mutex 保护g_b_ret_val,将其用于“等待”标志也是合适的——在这里添加另一个互斥体只会为问题提供更多机会。所以现在我们得到:

    pthread_mutex_lock(&g_ret_val_mutex);
    for (retry = 0; retry < MAX_RETRY && !g_b_ret_val; retry++) {
        (void)invoke_async_api();
        compute_wakeup_time(&abstime);
        g_waiting = true;
        pthread_cond_timedwait(&g_cond_t, &g_ret_val_mutex, &abstime);
        if (g_waiting) {
            /* timeout occurred, we never got our callback */
            /* may want something special for this case */
        } else {
            /* wakeup occurred, result is in g_b_ret_val */
        }
    }
    success = g_b_ret_val;
    /* or: success = retry < MAX_RETRY; */
    g_waiting = false;
    pthread_mutex_unlock(&g_ret_val_mutex);
    

    同时:

    callback_routine() /* Thread-T2 */
    {
        pthread_mutex_lock(&g_ret_val_mutex);
        g_b_ret_val = compute_success_or_failure();
        if (g_waiting) {
            g_waiting = false;
            pthread_cond_signal(&g_cond_t);
        }
        pthread_mutex_unlock(&g_ret_val_mutex);
    }
    

    我已经将signal 移动到互斥锁的“内部”,尽管这两种方式都可以,所以只有在设置了g_waiting 并清除g_waiting 时我才能这样做。由于我们持有互斥体,因此可以在调用 pthread_cond_signal 之前或之后清除 g_waiting(只要没有其他代码会中断序列)。

    注意:如果我们确实开始使用timedwait,我们需要确定在使用另一个更早的invoke但在超时之前没有返回结果时调用invoke_async_api是否可以。

    【讨论】:

      猜你喜欢
      • 2018-04-10
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      • 2017-05-16
      • 1970-01-01
      • 2020-04-04
      • 2011-12-26
      • 1970-01-01
      相关资源
      最近更新 更多