如何找到是哪个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.判断如果没有读者占用资源了,则判断是否有写锁请求,如果有优先唤醒退出 否则唤醒读锁请求

 

posted @ 2022-08-03 19:00  codestacklinuxer  阅读(57)  评论(0)    收藏  举报