今天有用户反馈,mac微信卡死在登录界面,彩球一直转。从现象来看,是主线程卡住了,难道是死循环了?进一步地,开启调试符号("Build Settings"->"Strip Linked Product"置为NO),发现console里面狂刷主线程卡顿监控日志,主要堆栈如下:
[I][][MMLagMonitor.mm, startMonitorWithCallback, 179]][INFO: Backtrace of Thread 775:
libsystem_kernel.dylib 0x7fff9a50ac22 __psynch_mutexwait + 10
Foundation 0x7fff866710e2 -[NSLock lock] + 145
WeChat 0x108e7bc68 -[MMCGINotifyCenter addDelegateInternal:sessionId:] + 59
WeChat 0x108e7bbef +[MMCGINotifyCenter addDelegate:sessionId:] + 83
WeChat 0x108a03630 -[MMCGI registerDelegate:] + 37
WeChat 0x108a034d8 -[MMCGI initWithItem:delegate:] + 140
WeChat 0x10889a16d -[MMCGIService RequestCGI:delegate:] + 302
WeChat 0x108e29126 -[AuthCGI QRCodeAuth:withPassword:] + 3859
WeChat 0x108e9c1d0 -[AccountService QRCodeLoginWithUserName:password:] + 58
WeChat 0x108dc5039 __34-[QRCodeLoginLogic setupQRCodeCGI]_block_invoke.38 + 330
WeChat 0x108b3f1e3 -[QRCodeLoginCG<…>
可以看到,NSLock一直在锁等待,即死锁了。 那么死锁是怎么发生的呢?我们知道,NSLock是非递归锁,当同一线程重复获取同一非递归锁时,就会发生死锁。如下所示:
NSLock *m_lock;
[m_lock lock]; // 成功上锁
do something....
[m_lock lock]; // 上面已经上锁,这里阻塞等待锁释放,不会再执行下面,锁永远得不到释放,即死锁
do something....
[m_lock unlock]; // 不会执行到
do something....
[m_lock unlock];
于是,断点跟踪堆栈中相关问题函数的调用关系,企图找到如上所示的嵌套上锁代码,结果如下:
简化如下:
- (void)erase {
[m_lock lock];
dealloc();
[m_lock unlock];
}
- (void)dealloc {
erase();
}
另一方面,用NSRecursiveLock或者@synchronized替代NSLock,就可以成功登录了。因为同一线程重复获取同一递归锁,不会发生死锁。