基于glibc-2.11.2的thread_barrier_wait研究

本文详细分析了glibc-2.11.2中pthread_barrier_wait的实现,包括其内部的锁机制、线程同步及futex操作。通过代码解读,解释了在多线程环境下如何处理barrier等待,特别是当线程在barrier处等待、唤醒以及其他线程试图进入下一个barrier时的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近调的一个程序老在pthread_barrier_wait的futex(.., FUTEX_WAIT, ...)处死锁,于是找来glibc的源代码,结合调试,研究了一把这个函数,下面谈谈我的理解。

 

 


 

图1. pthread_barrier_wait的基本代码

 

图2. pthread_barrier_wait真正实现

 

glibc的pthread_barrier_wait其实在../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/pthread_barrier_wait.S:31用汇编实现的,但是要表达的意思跟../nptl/pthread_barrier_wait.C的C语言实现一个样。

假设程序有4个线程,设有两个barrier。

首先对barrier的操作用mutex保护起来,如图1第9行所示,使用了ibarrier->lock这个整数当mutex。

结构体指针barrier指向的结构体如下所示

 

struct pthread_barrier

{

  unsigned int curr_event;

  int lock;

  unsigned int left;

  unsigned int init_count;

  int private;

}

在pthread_barrier_init中初始化成了

 

{curr_event = 0, lock = 0, left = 4, init_count = 4, private = 128}

 

 

在第一个barrier处,假设线程T1, T2, T3, T4按照先后顺序执行了pthread_barrier_wait,且在第一个barrier处没有两个线程同时等待在lock处,因此T1, T2, T3在获取了lock后,把left--然后释放lock进入futex(&curr_event, FUTEX_WAIT_PRIVATE /*==128*/, 0, NULL)睡眠,T4在获取lock把left--后,发现left为0了,因此执行futex(&cur_event, FUTEX_WAKE_PRIVATE /*129*/, 0x7FFFFFFF)唤醒T1, T2, T3, T4,在这之前把++cur_event,以便下次进入barrier与这次区分,T4返回PTHREAD_BARRIER_SERIAL_THREAD == -1,其他线程返回0,注意到T4还没有释放lock,而这个lock是用最后一个离开barrier的线程释放的,如图1第42所示,每个线程在离开barrier时会把left++,如图1第40行所示。

假设还有线程没有退出第一个barrier,因此lock == 1,这时退出的线程在进入第二个barrier时由图2的第16行->121->__lll_lock_wait(../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/lowlevellock.S, line 123.)。__lll_lock_wait如图3所示

 

图3. __lll_lock_wait代码

 

假设T2, T3, T4都离开了第一个barrier,T1还没有离开一个barrier,barrier的要求就是在所有的线程离开上一个barrier后才能进入下一个barrier,因此T2, T3, T4沿着上面的路径进入__lll_lock_wait并lock设为2,然后futex(&lock, 128, 2, NULL)进入睡眠。

这时T1要离开了第一个barrier了,在把lock--后,如图2第68行,发现lock不为0,因此69行->135行->__lll_unlock_wake。

(如果是T4最后一个离开第一个barrier则为110行->123行->__lll_unlock_wake。总之都是进入了__lll_unlock_wake。)我们发现133行也有一个__lll_unlock_wake,它的进入路径为33行->130行->__lll_unlock_wake,这在什么情况下发生呢?

请看下面分析。

 

图4. __lll_unlock_wake代码

 

 

 

__lll_unlock_wake在../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/lowlevellock.S, line 363,如途所示,

唤醒其他线程前把lock设为了0,图4第17行,然后futex(&lock, 129, 1)唤醒一个线程。醒来的线程沿着图2第122行->19行->33行->130行->__lll_unlock_wake继续唤醒下一个线程。

可以得出下面结论:

1)进入图2的9:的是在这个barrier执行了futex(&curr_event, FUTEX_WAIT_PRIVATE /*==128*/, 0, NULL),退出时发现有线程试图进入下一个barrier而在lock上进入睡眠,因而去唤醒其中的一个线程。

2)进入图2的4:的是在这barrier执行了futex(&cur_event, FUTEX_WAKE_PRIVATE /*129*/, 0x7FFFFFFF),退出时发现有线程试图进入下一个barrier而在lock上进入睡眠,因而去唤醒其中的一个线程。

3)进入图2的6:的是试图进入barrier而发现还有线程没有结束上个barrier而在lock进入睡眠的线程被唤醒后再去唤醒其他的在lock上睡眠的线程。或者是试图进入第一个barrier获取lock后,发现有试图进入这个barrier而在lock上睡眠的线程,因而要去唤醒睡眠的线程。这里回答了上面的问题,也就是说,在获取lock执行一系列操作,然后在释放lock时发现有在lock上睡眠的线程,因此进入6。

 

这三类线程回跳的地址有很大的差别,因为1)2)还没有结束第一个barrier,而3)已经结束了第一个barrier,1)为在curr_event上睡眠的线程,而2)为唤醒所有的在curr_event上睡眠的线程,两者要求的返回值不同。因此

1)回跳到10:,返回0

2)回跳到5:,返回-1

3)回跳到7:,在curr_event上进入睡眠。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值