如何找到是哪个rwlock导致竞争 && pthread_rwlock 分析
目前通过perf top 发现cpu主要耗在futex 上去了
-99.56%-- _raw_spin_lock | | | |--55.23%-- futex_wake | | do_futex | | sys_futex | | system_call_fastpath | | | | | |--99.99%-- __lll_unlock_wake | | --0.01%-- [...]
可知是应用层调用mutex 等导致;但是是哪个mutex 变量导致呢?
此时应该怎样分析呢?
分析如下:
使用strace 跟踪futex 查看对应传入的参数!
[pid 17919] <... futex resumed> ) = 0 [pid 17919] futex(0x7fc31c0059f0, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 17903] <... futex resumed> ) = 1 [pid 17920] futex(0x7366e0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...> [pid 17917] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1 <unfinished ...> [pid 17920] <... futex resumed> ) = -1 EAGAIN (Resource temporarily unavailable) [pid 17917] <... futex resumed> ) = 0 [pid 17920] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 17900] futex(0x7366e0, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable) [pid 17900] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 17899] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 17920] futex(0x7366e0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...> [pid 17914] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1 <unfinished ...> [pid 17920] <... futex resumed> ) = -1 EAGAIN (Resource temporarily unavailable) [pid 17914] <... futex resumed> ) = 0 [pid 17920] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 [pid 17923] futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0
发现futex 使用变量的地址是0x7366e0
那么其名称是啥呢
此时使用gdb了
gdb attach 进去
(gdb) info symbol 0x7366e0 __init_array_start + 53736 in section .tbss of /opt//bin/afd_org g_website_table_rwlock in section .bss of /opt//bin/afd_org
可以看到变量名称就是 g_website_table_rwlock 导致;但是strace 的同时发现还有一个也使用了rwlock
[pid 66684] 10:22:26.522578 futex(0x7366e0, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000137> [pid 66684] 10:22:26.522805 futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000066> [pid 66682] 10:22:26.524469 futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000012> [pid 66686] 10:22:26.526302 futex(0x7366e0, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000029> [pid 66690] 10:22:26.531962 futex(0x7fc31c0059f0, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000010> [pid 66684] 10:22:26.532072 futex(0x7fc31c0059f0, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000028> [pid 66684] 10:22:26.532318 futex(0x7fc31c0059f0, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000032> [pid 66704] 10:22:26.535722 futex(0x7fc37577eb40, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.000006> [pid 66691] 10:22:26.535755 futex(0x7fc37577eb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...> [pid 66704] 10:22:26.535782 futex(0x7fc37577eb40, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000006>
但是gdb info symbol 没有结果??
这个应该怎样处理??
同时很奇怪 根据打印当前变量结果都是readers
p g_website_table_rwlock $1 = {__data = {__lock = 0, __nr_readers = 21, __readers_wakeup = 0, __writer_wakeup = 0, __nr_readers_queued = 0, __nr_writers_queued = 0, __writer = 0, __shared = 0, __pad1 = 0, __pad2 = 0, __flags = 1}, __size = "\000\000\000\000\025", '\000' <repeats 43 times>, "\001\000\000\000\000\000\000", __align = 90194313216}
但是rwlock_unlock的时候去调用了unlock_wait最后调用了futex??
#0 0x00007fc37710a9d5 in __lll_unlock_wake () from /usr/lib/libpthread.so.0 #1 0x00007fc377107c3a in pthread_rwlock_unlock () from /usr/lib/libpthread.so.0
#define lll_unlock(futex, private) \ (void) \ ({ int ignore; \ if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) \ __asm __volatile (__lll_unlock_asm_start \ ".subsection 1\n\t" \ ".type _L_unlock_%=, @function\n" \ "_L_unlock_%=:\n" \ "1:\tleaq %0, %%rdi\n" \ "2:\tsubq $128, %%rsp\n" \ "3:\tcallq __lll_unlock_wake_private\n" \ "4:\taddq $128, %%rsp\n" \ "5:\tjmp 24f\n" \ "6:\t.size _L_unlock_%=, 6b-1b\n\t" \ ".previous\n" \ LLL_STUB_UNWIND_INFO_5 \ "24:" \ : "=m" (futex), "=&D" (ignore) \ : "m" (futex) \ : "ax", "cx", "r11", "cc", "memory"); \ else \ __asm __volatile (__lll_unlock_asm_start \ ".subsection 1\n\t" \ ".type _L_unlock_%=, @function\n" \ "_L_unlock_%=:\n" \ "1:\tleaq %0, %%rdi\n" \ "2:\tsubq $128, %%rsp\n" \ "3:\tcallq __lll_unlock_wake\n" \ "4:\taddq $128, %%rsp\n" \ "5:\tjmp 24f\n" \ "6:\t.size _L_unlock_%=, 6b-1b\n\t" \ ".previous\n" \ LLL_STUB_UNWIND_INFO_5 \ "24:" \ : "=m" (futex), "=&D" (ignore) \ : "m" (futex), "S" (private) \ : "ax", "cx", "r11", "cc", "memory"
#define lll_unlock(futex, private) \ (void) \ ({ int ignore; \ if (__builtin_constant_p (private) && (private) == LLL_PRIVATE) \ __asm __volatile (__lll_unlock_asm_start \ ".subsection 1\n\t" \ ".type _L_unlock_%=, @function\n" \ "_L_unlock_%=:\n" \ "1:\tleaq %0, %%rdi\n" \ "2:\tsubq $128, %%rsp\n" \ "3:\tcallq __lll_unlock_wake_private\n" \ "4:\taddq $128, %%rsp\n" \ "5:\tjmp 24f\n" \ "6:\t.size _L_unlock_%=, 6b-1b\n\t" \ ".previous\n" \ LLL_STUB_UNWIND_INFO_5 \ "24:" \ : "=m" (futex), "=&D" (ignore) \ : "m" (futex) \ : "ax", "cx", "r11", "cc", "memory"); \ else \ __asm __volatile (__lll_unlock_asm_start \ ".subsection 1\n\t" \ ".type _L_unlock_%=, @function\n" \ "_L_unlock_%=:\n" \ "1:\tleaq %0, %%rdi\n" \ "2:\tsubq $128, %%rsp\n" \ "3:\tcallq __lll_unlock_wake\n" \ "4:\taddq $128, %%rsp\n" \ "5:\tjmp 24f\n" \ "6:\t.size _L_unlock_%=, 6b-1b\n\t" \ ".previous\n" \ LLL_STUB_UNWIND_INFO_5 \ "24:" \ : "=m" (futex), "=&D" (ignore) \ : "m" (futex), "S" (private) \ : "ax", "cx", "r11", "cc", "memory"); \ })
上述代码为lll_unlock的宏定义----确实有可能调用__lll_unlock_wake;
但是 走到第二分支才会出现;第一分支 __builtin_constant_p是GCC的内建函数,可以用该函数判断传入的参数是否为编译时常量。如果是编译时常量则该函数返回1,否则返回0。如果该函数返回0,并不代表参数不是常数,而仅仅是在“-O”优化参数下,GCC编译器无法证明其是否为常量。
LLL_PRIVATE 值为0 lll_unlock传入的值也为0, 但是没有走入!!!!
是这个原因???
以前分析过pthread_mutex见之前文章
现在来分析一下 ptread_rwlock的实现
目前找到一份 rwlock的实现代码,见nptl实现Native POSIX Thread Library
目前读写锁中一个重要的问题是:防止饿死现象;
1,总体大原则还是:一次只有一个writer可以占有写模式的读写锁,但是多个reader可用同时占有读模式的读写锁。
2,如果有reader在占用锁,那么其它的很多reader继续来使用,如果reader量真的很大的时候,那么writer可能会饿死,一直得不到执行。这里可以在rdlock中看到。
3,如果有writer在占用锁,后面又来了很多writer,那么reader会一直得不到执行,reader可能会饿死
目前看有的版本是已经将读写锁设计成顺序锁那种,启用了排队逻辑
二. 读写锁API
1. 读写锁属性
读写锁属性(pthread_rwlockattr_t)有两种: lockkind和pshared。
(1)lockkind:读写策略,包括读取优先(默认属性)、写入优先。
读取优先:如果在写锁请求后面到来的读锁请求不被写锁请求阻塞。如果读锁请求前仆后继源源不断地到来,只要有
一个读锁没完成,写锁就没分。 该策略会导致较早到的写锁饿死。
写入优先:一旦线程申请写锁,在写锁请求后面到来的读锁请求就会统统被阻塞,不能先于写请求拿到锁。
enum { PTHREAD_RWLOCK_PREFER_READER_NP, //读者优先(默认属性) PTHREAD_RWLOCK_PREFER_WRITER_NP, //读者优先 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, //写者优先 PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP //读者优先 };
static bool rwlock_attr_initialized = false; static pthread_rwlockattr_t rwlock_attr; if (!rwlock_attr_initialized) { pthread_rwlockattr_init(&rwlock_attr); pthread_rwlockattr_setkind_np(&rwlock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); } return pthread_rwlock_init(rwlock, &rwlock_attr);
看下部分glibc ntpl pthread的逻辑
typedef union 151 { 152 # if __WORDSIZE == 64 153 struct 154 { 155 int __lock;//管理读写锁全局竞争的锁,无论是读锁写锁还是解锁,都会互斥 156 unsigned int __nr_readers; //字段表示当前有多少个reader正在使用lock; 157 unsigned int __readers_wakeup;//表示需要被唤醒的reader的个数 158 unsigned int __writer_wakeup;//表示需要被唤醒的writer的个数 159 unsigned int __nr_readers_queued;//表示正在排队等待锁的reader个数 160 unsigned int __nr_writers_queued;//正在排队等待锁的writer的个数 161 int __writer;//示正在使用锁的writer的线程id,用来防止死锁 非0 表示被写者占用 162 int __shared; 163 unsigned long int __pad1; 164 unsigned long int __pad2; 165 /* FLAGS must stay at this position in the structure to maintain 166 binary compatibility. */ 167 unsigned int __flags; 168 } __data; 169 # else 170 struct 171 { 172 int __lock; 173 unsigned int __nr_readers; 174 unsigned int __readers_wakeup; 175 unsigned int __writer_wakeup; 176 unsigned int __nr_readers_queued; 177 unsigned int __nr_writers_queued; 178 /* FLAGS must stay at this position in the structure to maintain 179 binary compatibility. */ 180 unsigned char __flags; 181 unsigned char __shared; 182 unsigned char __pad1; 183 unsigned char __pad2; 184 int __writer; 185 } __data; 186 # endif 187 char __size[__SIZEOF_PTHREAD_RWLOCK_T]; 188 long int __align; 189 } pthread_rwlock_t;
无论是申请读锁还是申请写锁,还是解锁,都至少会做一次全局互斥锁(对应__lock)的加锁和解锁,若不考虑阻塞,单单考虑 操作本身的开销,读写锁的加解锁开销是互斥锁的两倍。当然,函数结束前或进入阻塞之前,会将全局的互斥锁释放。
1. 对于读锁请求而言,如果:
(I) 无线程持有写锁,即__writer==0
(II) 采用的是读者优先策略或没有写锁的等待者(__nr_writers_queued==0)
当满足这两个条件时,读锁请求都可以立即获得读锁,返回之前执行__nr_readers++,表示增加了一个线程占有读锁。
不满足的话,则执行__nr_readers_queued++,表示增加一个读锁等待者,然后调用futex,陷入阻塞。醒来之后,会执行
__nr_readers_queued–,然后再次判断是否同时满足条件1和2
2. 对于写锁请求而言,如果:
(I) 无线程持有写锁,即__writer==0
(II) 没有线程持有读锁,即__nr_readers==0
只要满足上述条件,就会立刻拿到写锁,将__writer置为线程的ID(调度域)
如果不满足,那么执行__nr_writers_queued++,表示增加一个写锁等待者线程,然后执行mutex陷入等待。醒来后,先执行
__nr_writers_queued–,然后重新判断条件1和2。
3. 对于解锁而言,如果当前锁是写锁,则执行如下操作:
执行__writer=0,表示释放写锁
根据__nr_writers_queued判断有没有写锁等待者,如果有则唤醒一个写锁等待者
如果没有写锁等待者,则判断有没有读锁等待者;如果有,则将所有的读锁等待者一起唤醒。
如果当前锁是读锁,则执行如下操作:
执行__nr_readers–,表示读锁占有者少了一个
判断__nr_readers是否等于0,是的话则表示自己是最后一个读锁占有者,需要唤醒写锁等待者或者读锁等待者:
根据__nr_writers_queued判断是否存在写锁等待者,若有,则唤醒一个写锁等待线程
如果没有写锁等待者,判断是否存在读锁等待者,若有,则唤醒所有的读锁等待者
- rwlock->__data.__writer:非0表示当前被写者占用
- rwlock->__data.__nr_readers:读者共享占用资源的读者数量
- rwlock->__data.__nr_writers_queued:写者的锁请求数
- rwlock->__data.__nr_readers_queued:读者的锁请求数
写锁执行流程:
- 1.lock锁定 (实现对一些公共的控制标志的顺序访问)
- 2.判断如果没有写者也没有读者在访问共享资源则将写者rwlock->__data.__writer 标记成自己.非零值 退出循环的自旋方式 lock解锁
- 3. 如果有别的在访问共享资源 则将写队列请求数加一(++rwlock->__data.__nr_writers_queued)
表示有写请求要访问资源,然后释放lock锁定 等待其他的已经占用的线程访问完成。
4.其他的访问资源 完成后,在pthread_rwlock_unlock中判断如果没有读者占用了,唤醒第3步的等待
5.重新再lock锁定,将写者锁定请求数减一,重新回到2处判断。这时因为其他的读者锁请求没有唤醒 所以优先第2步就执行了也就是满足了读写锁的特征3.
读锁执行流程:
1.lock锁定 (实现对一些公共的控制标志的顺序访问)
2.判断如果没有写者且没有写者锁请求
则将读者++rwlock->__data.__nr_readers 退出循环的自旋方式 lock解锁
3. 如果不符合条件 也就是有写者在占用共享资源或者有写锁请求在申请,则将读队列请求数加一(++rwlock->__data.__nr_readers_queued)
表示有读者锁请求要访问资源,然后释放lock锁定 等待其他的已经占用的线程访问完成(其实就是等待写者释放占用)。
4.写者访问资源 完成后,在pthread_rwlock_unlock中判断如果没有读者占用了,唤醒第3步的等待
5.重新再lock锁定,将读者锁定请求数减一,重新回到2处判断。
解锁执行流程:
1.判断如果有写者标识 则将其置0 rwlock->__data.__writer =0
2.否则将读者拥有的减1 rwlock->__data.__nr_readers--
3.判断如果没有读者占用资源了,则判断是否有写锁请求,如果有优先唤醒退出 否则唤醒读锁请求

浙公网安备 33010602011771号