rwlock未初始化引起的问题

描述一次在工作中遇到的简单问题,其现象以及排查流程

读写锁pthread_rwlock

问题描述

程序死锁,gstack调用栈,发现pthread_rwlock_rdlock死锁。

Thread 3 (Thread 0x7f50e76fa700 (LWP 28329)):
#0  0x00007f514f2e9184 in pthread_rwlock_rdlock () from /usr/lib64/libpthread.so.0
#1  0x00000000004fbd33 in UDESessionImplement::_check_loop (this=0x2506990) at ude_session.cpp:886
#2  0x00000000004fbb80 in UDESessionImplement::_check_thread (arg=0x2506990) at ude_session.cpp:838
#3  0x00007f514f2e5ea5 in start_thread () from /usr/lib64/libpthread.so.0
#4  0x00007f514f00e9fd in clone () from /usr/lib64/libc.so.6

Thread 1 (Thread 0x7f51542aecc0 (LWP 28268)):
#0  0x00007f514f2e9184 in pthread_rwlock_rdlock () from /usr/lib64/libpthread.so.0
#1  0x00000000004fb716 in UDESessionImplement::_chose_link (this=0x2506990, param=0x7ffff98d0930, table_name=0x0)
    at ude_session.cpp:743
#2  0x00000000004fa6e0 in UDESessionImplement::handle_request (this=0x2506990,
    req_data=0x7ffff98d0930 "{\"msgId\":\"1\",\"opCode\":\"11\",\"data\":{\"tableInfo\":\"SYN_D_NCH_INIT_NODE\"}}", req_len=71,
    rsp_data=0x25193a0 "", rsp_len=@0x7ffff98d1610: 4096, timeout=3) at ude_session.cpp:378
#3  0x00000000004ee01b in Sync_Client::GetFirst (this=0xeb8640 <DGWSyncClient::InitSyncClient(int)::_client>,
    sname=0xeb7e00 <sync_nchInfo()::nchInit> "SYN_D_NCH_INIT_NODE", data=0x25193a0 "", len=@0x7ffff98d166c: 4096,
    seq=@0x7ffff98d1668: -1, timeout=3) at sync_client.cpp:531

故障分析

打印读写锁的值:

p _connect_locker $1 = {__data = {__lock = 0, __nr_readers = 0, __readers_wakeup = 3544, __writer_wakeup = 393220, __nr_readers_queued = 1094998026, __nr_writers_queued = 1213417806, __writer = 0, __shared = 0, __pad1 = 0, __pad2 = 0, __flags = 3456}, __size = "\000\000\000\000\000\000\000\000\330\r\000\000\004\000\006\000\nXDANISH", '\000' <repeats 24 times>, "\200\r\000\000\003\000\a", __align = 0}
  • 读写锁中各个字段的含义:
变量 说明
__lock 管理读写锁全局竞争的锁,无论是读锁写锁还是解锁,都会互斥
__writer 写锁持有者的线程ID,如果为0则表示当前无线程持有写锁
__nr_reads 读锁持有线程的个数
__nr_reads_queued 读锁的派对等待线程的个数
__nr_writers_queued 写锁的排队等待线程的个数

读写锁的逻辑

  1. 对于读锁请求
    • 无线程持有写锁,即__writer==0
    • 采用的是读者优先策略或没有写锁的等待者(__nr_writers_queued==0) 当满足这两个条件时,读锁请求都可以立即获得读锁,返回之前执行__nr_readers++,表示增加了一个线程占有读锁。 不满足的话,则执行__nr_readers_queued++,表示增加一个读锁等待者,然后调用futex,陷入阻塞。醒来之后,会执行__nr_readers_queued–,然后再次判断是否同时满足条件1和2。
  2. 对于写锁请求
    • 无线程持有写锁,即__writer==0
    • 没有线程持有读锁,即__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判断是否存在写锁等待者,若有,则唤醒一个写锁等待线程;如果没有写锁等待者,判断是否存在读锁等待者,若有,则唤醒所有的读锁等待者。

## 故障结论 从读写锁的逻辑看出,如果在读锁加锁的时候,__nr_writers_queued不为0,那么就会阻塞读锁。从打印看出__nr_readers为0,__writer=0表示没有线程持有读锁,也没有线程持有写锁,但是读写锁的值却异常了。只有两种可能,一是未初始化,二是内存被改写。走查代码,发现两个构造函数中,只有一个构造函数初始化了读写锁。

Tags: problems
Share: X (Twitter) Facebook LinkedIn