最近在项目中又遇到了一个死锁问题,跟NSNotificationCenter有关,这个类平时不知道咱们有没有留意过,postNotication的时候是加锁的,假如是在主线程注册对应的监听办法,而此时主线程又在等候另一个线程,则会导致发送告诉的线程堵塞。假如发送告诉的线程跟等候的线程是同一个,就会导致死锁。
具体情况是这样的,我在项目中运用了libevent库,他有两种运转形式:同步和异步。 假如是同步,则所有操作都在一个线程上履行,假如是异步,则在不同线程上履行,为了防止多线程一起拜访同一个导致的溃散,咱们申明晰对内部线程进行加锁,即evthread_use_pthreads。
然后咱们项目里面申明晰一个告诉kP2PConnectStatusChangeNoti, 这个告诉的回调办法是在MGCameraCollectionViewCell的awakeFromNib办法中注册的(主线程),作用是监听P2P衔接的状态,显现对应的UI
然后告诉发送的代码是在libevent的一个回调办法session_connect_success中,这个回调办法在p2p衔接建立成功后履行
我在脱离某个页面的时候,会触发destroy办法,这个办法在主线程履行,然后内部会调用libevent的某些办法,这时候问题来了,当我反复操作脱离页面,进入页面(会触发p2p衔接的建立)多次后,就呈现了死锁。见下图,thread 1 在等候libevent开释锁,livevent在等候thread21 的session_connect_success办法履行完结,而thread21在调用setConnectStatus时,发送kP2PConnectStatusChangeNoti对线程进行了加锁,这个锁是NSNotificationCenter内部加的,我没有具体源码,但是猜测应该是锁住了主线程。 为什么这么说呢? 由于从断点时各线程的情况来看,只有thread1 (主线程)和thread21(libevent线程)在wait,其他线程都没有wait,显然是互相等候导致了死锁。 这儿需求留意一点,libevent持有的锁跟NSnotificationCenter内部的锁不是同一个。
咱们尝试在主线程异步发送kP2PConnectStatusChangeNoti告诉,看还会不会死锁。经过实验证明,这样确实不会死锁,由于是异步的,libevent线程不会被主线程堵塞,也就能顺利的完结event的开释,从而主线程也能顺利运转下去。
那为什么NSNotificationCenter要对postNotification加锁呢?我猜我想应该是为了防止发送告诉时,有其他线程对观察者进行增加和移除吧,那样会导致一些意想不到的事情产生,或许是为了防止多线程一起操作内部队列引发数据纷歧致的问题。我觉得前一种可能性更大,一起addObserver办法和removeObserver内部应该也进行了加锁,这样才干更好的处理多线程一起操作。假如是这样的话,就可以解说为什么postNotification会堵塞主线程了,由于在频繁进入和脱离页面的时候,频繁调用了addObserver和removeObserver办法,他们主线程上加了锁(严格来说,removeObserver纷歧定是在主线程调用,由于这儿是编译器自己加上去的,咱们并没有显现调用),而postNotification在等候他们开释锁,间接引起了发送告诉的线程堵塞。
由此,咱们可以得出结论,运用NSNotificationCenter时,发送告诉和给告诉增加观察者的线程最好是同一个,这样可以防止加锁导致的线程堵塞和App卡死。