c - Ubuntu : sem_timedwait not waking (C)

标签 c linux pthreads semaphore ubuntu-9.04

我有 3 个进程需要同步。进程一做某事然后唤醒进程二并休眠,进程二做某事然后唤醒进程三并休眠,进程三做某事并唤醒进程一并休眠。整个循环定时运行大约 25hz(由外部同步到进程一之前触发我的“真实”应用程序中的进程二引起)。我使用 sem_post 来触发(唤醒)每个进程,并使用 sem_timedwait() 来等待触发。

这一切都成功地运行了几个小时。然而,在某个随机时间(通常在两到四个小时之间的某处之后),其中一个进程在 sem_timedwait() 中开始超时,即使我确信信号量是通过 sem_post() 触发的。为了证明这一点,我什至在超时后立即使用了 sem_getvalue(),并且值为 1,因此应该触发了 timedwait。

请看下面的代码:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <semaphore.h>

sem_t trigger_sem1, trigger_sem2, trigger_sem3;

// The main thread process.  Called three times with a different num arg - 1, 2 or 3.
void *thread(void *arg)
{
  int num = (int) arg;
  sem_t *wait, *trigger;
  int val, retval;
  struct timespec ts;
  struct timeval tv;

  switch (num)
    {
      case 1:
        wait = &trigger_sem1;
        trigger = &trigger_sem2;
        break;
      case 2:
        wait = &trigger_sem2;
        trigger = &trigger_sem3;
        break;
      case 3:
        wait = &trigger_sem3;
        trigger = &trigger_sem1;
        break;
    }

  while (1)
    {
      // The first thread delays by 40ms to time the whole loop.  
      // This is an external sync in the real app.
      if (num == 1)   
        usleep(40000);

      // print sem value before we wait.  If this is 1, sem_timedwait() will
      // return immediately, otherwise it will block until sem_post() is called on this sem. 
      sem_getvalue(wait, &val);
      printf("sem%d wait sync sem%d. val before %d\n", num, num, val);

          // get current time and add half a second for timeout.
      gettimeofday(&tv, NULL);
      ts.tv_sec = tv.tv_sec;
      ts.tv_nsec = (tv.tv_usec + 500000);    // add half a second
      if (ts.tv_nsec > 1000000)
        {
          ts.tv_sec++;
          ts.tv_nsec -= 1000000;
        }
      ts.tv_nsec *= 1000;    /* convert to nanosecs */

      retval = sem_timedwait(wait, &ts);
      if (retval == -1)
        {
          // timed out.  Print value of sem now.  This should be 0, otherwise sem_timedwait
          // would have woken before timeout (unless the sem_post happened between the 
          // timeout and this call to sem_getvalue).
          sem_getvalue(wait, &val);
          printf("!!!!!!    sem%d sem_timedwait failed: %s, val now %d\n", 
            num, strerror(errno), val);
        }
      else
        printf("sem%d wakeup.\n", num);

        // get value of semaphore to trigger.  If it's 1, don't post as it has already been 
        // triggered and sem_timedwait on this sem *should* not block.
      sem_getvalue(trigger, &val);
      if (val <= 0)
        {
          printf("sem%d send sync sem%d. val before %d\n", num, (num == 3 ? 1 : num+1), val);
          sem_post(trigger);
        }
      else
        printf("!! sem%d not sending sync, val %d\n", num, val);
    }
}



int main(int argc, char *argv[])
{
  pthread_t t1, t2, t3;

   // create semaphores.  val of sem1 is 1 to trigger straight away and start the whole ball rolling.
  if (sem_init(&trigger_sem1, 0, 1) == -1)
    perror("Error creating trigger_listman semaphore");
  if (sem_init(&trigger_sem2, 0, 0) == -1)
    perror("Error creating trigger_comms semaphore");
  if (sem_init(&trigger_sem3, 0, 0) == -1)
    perror("Error creating trigger_vws semaphore");

  pthread_create(&t1, NULL, thread, (void *) 1);
  pthread_create(&t2, NULL, thread, (void *) 2);
  pthread_create(&t3, NULL, thread, (void *) 3);

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
}

当程序正确运行时(在开始时以及随机但很长一段时间后),将打印以下输出。 sem1 的值在 thread1 等待之前始终为 1,因为它休眠了 40ms,此时 sem3 已触发它,因此它会立即唤醒。其他两个线程等待,直到从前一个线程接收到信号量。

[...]
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
sem2 wakeup.
sem2 send sync sem3. val before 0
sem2 wait sync sem2. val before 0
sem3 wakeup.
sem3 send sync sem1. val before 0
sem3 wait sync sem3. val before 0
sem1 wait sync sem1. val before 1
sem1 wakeup.
sem1 send sync sem2. val before 0
[...]

但是,几个小时后,其中一个线程开始超时。从输出中可以看出信号量正在被触发,当我打印超时后的值时,它是1。所以sem_timedwait应该在超时之前就已经醒来了。我绝不会期望信号量的值在超时后为 1,除非触发发生在超时后但在我调用 sem_getvalue 之前,这种情况非常罕见(几乎肯定不会,但有可能发生)。

此外,一旦它开始失败,该信号量上的每个 sem_timedwait() 也会以同样的方式失败。请参阅以下输出,我对其进行了行编号:

01  sem3 wait sync sem3. val before 0
02  sem1 wakeup.
03  sem1 send sync sem2. val before 0
04  sem2 wakeup.
05  sem2 send sync sem3. val before 0
06  sem2 wait sync sem2. val before 0
07  sem1 wait sync sem1. val before 0
08  !!!!!!    sem3 sem_timedwait failed: Connection timed out, val now 1
09  sem3 send sync sem1. val before 0
10  sem3 wait sync sem3. val before 1
11  sem3 wakeup.
12  !! sem3 not sending sync, val 1
13  sem3 wait sync sem3. val before 0
14  sem1 wakeup.
[...]

在第 1 行,线程 3(我在 printf 中混淆地称为 sem3)等待 sem3 被触发。在第 5 行,thread2 为 sem3 调用 sem_post。然而,第8行显示sem3超时,但是信号量的值为1。thread3随后触发sem1并再次等待(10)。但是,因为该值已经是 1,所以它会立即唤醒。它不会再次发送 sem1,因为这一切都发生在将控制权交给 thread1 之前,但是它随后再次等待(val 现在为 0)并且 sem1 被唤醒。这现在一直重复,sem3 总是超时并显示值为 1。

所以,我的问题是为什么sem3超时了,即使信号量已经被触发并且值明明是1?我绝不会期望在输出中看到第 08 行。如果它超时(因为,比如线程 2 已崩溃或花费的时间太长),该值应为 0。为什么它在进入此状态之前先正常工作 3 或 4 小时?

我尝试过使用三个独立的程序进行类似的测试,通过共享内存进行通信,而不是在同一程序中使用三个线程。这更接近于我的真实世界应用程序。结果和输出是一样的。问题确实出现在信号量(特别是 sem_timedwait 调用)中,而不是与 pthreads 有任何关系。

我还尝试了更短和更长的延迟,以及完全消除延迟,结果与上述类似。完全没有延迟,它有时会在几分钟而不是几小时后开始产生错误。这当然意味着可以更快地重现问题。

这是使用内核为 2.6.28 的 Ubuntu 9.4。同样的程序在 Redhat 和 Fedora 上运行正常,但我现在正在尝试移植到 Ubuntu。我也尝试过使用 Ubuntu 9.10,但没有任何区别。

感谢您的任何建议, 吉尔斯

最佳答案

(很抱歉给出第二个答案,但是这个太乱了,无法通过编辑清理)

我认为答案已经在问题的原始帖子中。

So, my question is why does sem3 timeout, even though the semaphore has been triggered and the value is clearly 1? I would never expect to see line 08 in the output. If it times out (because, say thread 2 has crashed or is taking too long), the value should be 0. And why does it work fine for 3 or 4 hours first before getting into this state?

所以场景是:

  1. 线程 2 耗时太长
  2. sem3 在 sem_timedwait 中超时
  3. 线程 3 被取消调度或其他 它需要达到 sem_getvalue
  4. 线程 2 唤醒并执行它的 sem_postsem3
  5. 线程 3 发出它的 sem_getvalue 并看到一个 1
  6. thread 3个分支错了 分支并且不执行它的 sem_postsem1

这种竞争条件很难触发,基本上,您必须捕获一个线程在等待信号量时遇到问题的微小时间窗口,然后使用 sem_getvalue 读取信号量。这种情况的发生在很大程度上取决于环境(系统类型、内核数量、负载、IO 中断),因此这解释了为什么它只在下类后发生,如果根本不发生的话。

让控制流依赖于 sem_getvalue 通常不是一个好主意。对sem_t 的唯一原子 非阻塞访问是通过sem_postsem_trywait

所以这个问题中的示例代码具有竞争条件。这并不意味着 gillez 的原始问题代码确实具有相同的竞争条件。也许这个例子太简单了,对他来说仍然是同样的现象。

我的猜测是,在他最初的问题中有一个未保护 sem_wait。这是一个 sem_wait,它只检查其返回值,而不检查 errno,以防失败。如果进程有一些 IO,EINTR 确实会很自然地发生在 sem_wait 上。如果遇到 EINTR,您只需执行 do - while 检查并重置 errno

关于c - Ubuntu : sem_timedwait not waking (C),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2928571/

相关文章:

c++ - 如何处理 pam_authenticate 的 PAM_AUTHTOK_RECOVERY_ERR 返回值以获得有效的用户和密码?

c - 达到计数 32751 后无法创建线程

java - Java中的多个生产者和消费者问题(没有BlockingQueue)

c - Pthreads - 高内存使用率

linux - 使用脚本检查服务器配置

c - 错误信息 : *** glibc detected *** ./cube : double free or corruption (! prev): 0x0a4c4420 ***

c - 将指针(字符串)深入传递给函数的方式

c - 使用 openMP 并行化 C 中的嵌套循环

linux - 如何创建 Nagios 监视器

c - 使用c对链表进行选择排序