【问题标题】:Problem with pThread sync issuepThread 同步问题的问题
【发布时间】:2011-04-28 11:57:52
【问题描述】:

我正面临与 pthread 的同步问题。 threadWaitFunction1,是一个线程等待函数。 我希望行号。 247 flag = 1 仅在 243-246 完成后执行。 但我觉得奇怪的是,有时,它在 243-246 完成之前直接跳转到 247。

请帮帮我。

提前致谢。

236   struct timespec timeToWait;
237   static void* threadWaitFunction1(void *timeToWaitPtr)
238   {
239       cout << "Setting flag =0 inside threadWaitFunction1\n";
240       
241       cout << "Inside threadWaitFunction\n";
242       struct timespec *ptr = (struct timespec*) timeToWaitPtr;
243       pthread_mutex_lock(&timerMutex);
          flag = 0;
244       pthread_cond_timedwait(&timerCond, &timerMutex, ptr);
          flag=1;
245       pthread_mutex_unlock(&timerMutex);
246       cout << "Setting flag =1 inside threadWaitFunction1\n";
247       
248
249    }

创建和调用上述线程的线程是:

263  static void timer_trackStartTime ()
264  {
265       struct timeval now;
266       pthread_t thread;
267       
268       printf("Inside trackStartTime: flag = %d\n",flag);
269 
270      /* Setting timer expiration */
271       timeToWait.tv_sec = lt_leak_start_sec;;  // First expiry after 1 sec
272       timeToWait.tv_nsec = lt_leak_start_nsec;
273       pthread_create(&thread, NULL, threadWaitFunction1, &timeToWait);
274       pthread_join(thread, NULL);
275       //pthread_kill(thread, SIGKILL); // Destroying the thread to ensure no leaks
276 
.
.
283       }

如果我使用 pthread_mutex_lock 保护整个函数,但同样的问题仍然存在。如何保证有序执行?谁能帮忙?

编辑:now.tv_sec 和 now.tv_nsec 从代码中删除。 *编辑:更改了互斥锁内的标志(仍然不起作用)*

【问题讨论】:

  • 这个问题在目前的形式下是无法回答的,因为您没有说明您是如何衡量flag 何时/何地发生变化的。请注意,在您不持有互斥锁的情况下更改 flag 几乎肯定是虚假的,除非这是唯一可以访问 flag 的线程,在这种情况下,您的问题毫无意义。
  • @R:标志改变,只有当开始时间:当前时间+lt_leak_start_sec。我认为您错过了传递给线程函数的参数。
  • @R: 条件互斥锁被保持,直到达到结构中指定的时间。
  • 确保 (now.tv_usec * 1000) + lt_leak_start_nsec;不会溢出。您只能将 tv_nsec 设置为最大值 999999999,如果表达式大于此值,则应从 tv_nsec 中减去 999999999,并将 tv_sec 加 1。如果您的 timeToWaitPtr 包含无效的 tv_nsec(大于 999999999),pthread_cond_timedwait 将失败(您也应该检查它的返回值。)
  • @ninjalj:是的。 XBD 4.11 (pubs.opengroup.org/onlinepubs/9699919799/basedefs/…) 读取:“以下函数相对于其他线程同步内存”后跟一个包含pthread_mutex_lockpthread_mutex_unlock 的列表。

标签: c linux pthreads


【解决方案1】:

所以并不是真正的执行顺序(这很可能是正确的),而是让你不开心的时间。在“它在 243-246 完成之前直接跳转到 247”下,您的意思是“我观察到它在它应该在 244 中等待的时间过去之前执行了 247”。对吧?

然后,我怀疑这是spurious wakeup 的问题:即使没有其他线程发出条件变量信号,一个线程也可能被唤醒。 The specification of pthread_cond_timedwait() 表示“可能会发生来自 pthread_cond_timedwait() 或 pthread_cond_wait() 函数的虚假唤醒。”

通常,条件变量与应用程序中的某个事件相关联,而等待条件变量的线程实际上是在等待另一个线程发出感兴趣的事件已发生的信号。如果您没有事件并且只想等待一段时间,那么确实其他方式,例如usleep()timers 更合适,除非您还需要一个 pthread 取消点。

添加:由于您似乎对usleep() 很满意,并且只问为什么pthread_cond_timedwait() 没有达到您的预期,所以我决定不发布代码。如果需要,可以使用@Hasturkun 的答案。


ADDED-2:以下 cmets 中的输出(在应用 Hasturkun 的解决方案后获得)表明等待线程没有退出循环,这可能意味着 pthread_cond_timedwait() 返回的内容与 ETIMEDOUT 不同。你有没有看到@nos 对你帖子的评论(我固定了要减去的纳秒数量):

确保 (now.tv_usec * 1000) + lt_leak_start_nsec;不会溢出。您只能将 tv_nsec 设置为最大值 999999999,如果表达式大于该值,则应从 tv_nsec 中减去 1000000000,并将 tv_sec 增加 1。如果您的 timeToWaitPtr 包含无效的 tv_nsec(大于 999999999),pthread_cond_timedwait 将失败(您应该检查它的返回值也是。)– 4 月 28 日 19:04 号

在这种情况下,pthread_cond_timedwait() 将重复返回EINVAL,并且永远不会退出循环。最好在进入等待循环之前调整超时时间,虽然也可以响应EINVAL来完成。


ADDED-3:现在,在您更改问题中的代码以通过超时而不添加当前时间之后,它还有另一个问题。如the spec 中所述,pthread_cond_timedwait() 的超时时间是绝对时间,而不是相对时间;因此,当您通过类似 3 秒的超时时间时,它被解释为“自系统时间参考点以来的 3 秒”。那一刻几乎肯定已经过去了一段时间,所以pthread_cond_timedwait() 立即返回。
我建议您仔细阅读规范(包括基本原理),以更好地理解该函数的使用方式。

【讨论】:

  • @Alexey:很抱歉,使用ETIMEDOUT 也不起作用。五分之一的执行,仍然给出不正确的输出。
  • /tmp # ./a.out Constructor called # Measurement starts: 3.45 sec # Measurement ends at end of execution Inside Timer func lt_start_sec = 3 lt_start_nsec = 450000000 Inside trackStartTime: flag = 0 Inside threadWaitFunction new: registerAlloc won't be called as flag=0 new: registerAlloc won't be called as flag=0 new: registerAlloc won't be called as flag=0 new: Leaktracer registerAlloc won't be called as flag=0(附注:3.45 秒后标志应该是 1)但正如你所看到的都是“0”
  • 这个问题连续执行5次出现一两次。
  • @kingsmasher:我更新了我的答案;据说问题在于无效的超时值。
  • @kingsmasher:我更新了我的答案作为回应。基本上pthread_cond_timedwait()的超时参数必须使用绝对时间。
【解决方案2】:

Paul E. McKenney 写了一本名为 "Is Parallel Programming Hard, And, If So, What Can You Do About It?" 的书,其中包含关于记忆障碍的非常好的信息(和一些漂亮的图片)。

回到您的问题,flag 不受任何保护。虽然您可能认为 pthread_mutex_lock()pthread_mutex_unlock 提供了一些强大的排序和可见性保证,但它提供的唯一保证是对关键区域内的访问和互斥体本身。

此外,在某些架构上,pthread_mutex_lock() 使用获取屏障,pthread_mutex_unlock() 使用释放屏障,这意味着互斥保护区域之前和之后的访问可能会溢出到互斥保护区域。在释放互斥锁的 CPU 和获取相同互斥锁的另一个 CPU 之间提供了强大的排序保证,但几乎所有其他东西都不需要(也可能得不到)如此强大的保证。

编辑:

显然我对 pthread 的看法是错误的,它们似乎需要完整的内存屏障(如果您将 同步内存相对于其他线程解释为需要)。有关这方面的更多信息,以及有关在实际实现中提供的保证的一些信息,请访问 Reordering Constraints for Pthread-Style Locks Hans Boehm。

我还想知道 IA64 上的 NPTL 12

【讨论】:

  • 这不是真的。 POSIX 将pthread_mutex_lockpthread_mutex_unlock 以及所有其他与同步相关的函数指定为完整的内存屏障。有关详细信息,请参阅 XBD 第 4.11 节 (pubs.opengroup.org/onlinepubs/9699919799/basedefs/…)。如果您的代码可以只使用内存屏障而不使用锁,那么欢迎您实现一个普通的内存屏障作为 pthread_mutex_lockpthread_mutex_unlock 在一个永远不会被竞争的虚拟本地变量互斥锁上的包装器。
【解决方案3】:

正如 Alexey Kukanov 所说,问题很可能是虚假唤醒。您的代码可能会更正为循环,直到发生超时。请注意,我还将标志设置移动到互斥锁下。

static void* threadWaitFunction1(void *timeToWaitPtr)
{
    struct timespec *ptr = (struct timespec*) timeToWaitPtr;
    int ret;

    pthread_mutex_lock(&timerMutex);
    cout << "Setting flag =0 inside threadWaitFunction1\n";
    flag=0;
    cout << "Inside threadWaitFunction\n";
    while (pthread_cond_timedwait(&timerCond, &timerMutex, ptr) != ETIMEDOUT)
        ;
    cout << "Setting flag =1 inside threadWaitFunction1\n";
    flag=1;
    pthread_mutex_unlock(&timerMutex);
}

为了安全起见,您应该检查同一互斥体下的标志以建立排序

【讨论】:

  • 不幸的是,这并不容易。您每次都以相同的超时等待,这使得整个等待时间可能超过超时。相反,在每次唤醒时,您需要检查已经过去了多少时间,并适当地调整超时。这是更多的代码:)
  • 不,我不正确,因为pthread_cond_timedwait() 需要绝对时间。支持你的答案。
  • @Hasturkun:在pthread_mutex_lock 中移动标志没有效果,我在帖子中提到过,可能你错过了:)
  • @kingsmasher:为什么不赞成这个和我的答案?这似乎不公平。关键不是标志,而是while 循环调用pthread_cond_timedwait(),并检查结果。
  • @Husturkun:五分之一的执行,仍然给出错误的结果。
【解决方案4】:

这可能是因为编译器已经优化了一些东西,并将你的赋值放在线程互斥体之前的标志上。如果你想保证执行顺序,(通常不能保证的东西,唯一的条件是你的程序的可见行为不会因为优化而改变),你可以使用 内存屏障确保您希望按照编写顺序执行的指令仅按该顺序执行。

Here 是一篇非常有趣的文章,虽然技术性很强,而且很长,关于内存屏障如何工作以及它们做什么和不做什么的文章。它是为 Linux 编写的,但基本原理保持不变。

编辑:

锁是隐式内存屏障,通过我之前给出的链接,因此不需要内存屏障。

【讨论】:

  • @Tony:内存屏障?能不能说的详细一点?
  • @issssssssssssssshhhh 在我办公室的文章中访问问题。 :( 我会在家里检查,然后回复给你。如果你能在我当前的代码中添加一些代码sn-ps,这将是一个巨大的帮助。
  • 你真的应该认真阅读这样的事情,因为这不是一个容易的主题,只有真正的理解才能让你到达任何地方......
  • @Tony:当然可以,但现在无法访问此处的链接。这就是为什么我问你是否对此感到满意。
  • 我认为我们只是使用不同的词。根据您的用法,内存屏障是用于同步内存的特殊 cpu 指令。对我来说,内存屏障是正式语言/机器规范的一部分,在 x86 等不需要特殊操作码来确保其他线程可以看到写入的架构上,它可能会被简单地实现为什么都没有。
【解决方案5】:

仅供大家参考:

我使用pthread_cond_timedwait(&amp;timerCond, &amp;timerMutex, ptr); 无法实现的我使用usleep( ) 实现了,usleep 采用timespec 结构,我们可以使用秒和纳秒指定等待时间,我的目的就解决了。

那么pthread_cond_timedwait(&amp;timerCond, &amp;timerMutex, ptr); 有什么意义呢?我很惊讶,因为这个 API 预计会使调用线程等待,以满足该条件,但似乎处理器跳转到下一条指令作为优化措施,并且不会等待条件满足。

但我的问题还是一样,为什么pthread_cond_timedwait(&amp;timerCond, &amp;timerMutex, ptr);不应该让调用线程等待?

看来我在这个 API 后面浪费了一天时间:pthread_cond_timedwait( )

【讨论】:

  • 我认为您错误地将无效的timespec 传递给pthread_cond_wait。它返回什么值?
  • Alexey 的答案的结尾是正确的,这就是您的程序间歇性失败的原因。我希望你能奖励他+50。
猜你喜欢
  • 1970-01-01
  • 2019-08-12
  • 2022-01-17
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-08
  • 2011-07-15
相关资源
最近更新 更多