調(diào)試信號(hào)量dispatch_semaphore

使用三方庫CMVoipMulticastDelegate過程中糟把,發(fā)現(xiàn)此庫并沒有添加線程保護(hù)相關(guān)邏輯,會(huì)導(dǎo)致對(duì)delegateNodes操作時(shí)產(chǎn)生crash:Collection <__NSArrayM: 0xb550c30> was mutated while being enumerated惜傲, 這時(shí)由于在枚舉delegateNodes時(shí),恰好有對(duì)這個(gè)數(shù)組的操作缓艳,如增刪改等槽奕。
最懶的操作就是每次枚舉之前copy一份嘴纺,使用copy出來的數(shù)組進(jìn)行枚舉败晴,但這個(gè)方法存在數(shù)據(jù)錯(cuò)誤風(fēng)險(xiǎn)。最好的方式當(dāng)然是進(jìn)行線程安全的處理栽渴,基于性能考慮尖坤,我使用了信號(hào)量來處理。但是卻在其中引入錯(cuò)誤闲擦,初始 ** 錯(cuò)誤代碼 ** 如下:


@implementation CMVoipMulticastDelegate

- (id)init
{
    if ((self = [super init]))
    {
        delegateNodes = [[NSMutableArray alloc] init];
        signal = dispatch_semaphore_create(1);
        overTime = dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER);//DISPATCH_TIME_FOREVER;
        serialQueue = dispatch_queue_create("com.hycmcc.CMVoipMulticastDelegate", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)semaphore_signal_in_global_queue
{
    // signal始終放在一個(gè)線程中糖驴,一是保證不會(huì)因?yàn)閣ait和signal在一個(gè)線程導(dǎo)致死鎖僚祷,二是避免過多新建線程造成資源浪費(fèi)
    dispatch_async(serialQueue, ^{
        dispatch_semaphore_signal(signal);
    });
}

- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    if (delegateQueue == NULL) delegateQueue = dispatch_get_main_queue();
    
    CMVoipMulticastDelegateNode *node =
        [[CMVoipMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
    
    dispatch_semaphore_wait(signal, overTime);
    CMVoIPLogInfo(@"CMVoipMulticastDelegate addDelegate");
    [delegateNodes addObject:node];
    [self semaphore_signal_in_global_queue];
}

這份代碼在調(diào)試時(shí),還是會(huì)出現(xiàn)上面的crash贮缕,于是需要對(duì)其調(diào)試定位問題所在。

首先考慮肯定是線程鎖沒有起作用俺榆,那就斷點(diǎn)調(diào)試一下:


image.png
image.png

通過xcode斷點(diǎn)感昼,并在下面觀察區(qū),點(diǎn)擊signal屬性罐脊,右鍵選擇“print description of signa” 定嗓,可以在控制臺(tái)打印signal的值∑甲溃或者通過lldb指令:

(lldb) po signal
<OS_dispatch_semaphore: semaphore[0x1c02892e0] = { xref = 1, ref = 1, port = 0x25351, value = 0, orig = 1 }>

觀察到這個(gè)打印的值宵溅,那現(xiàn)在可以考慮是可以將其中這幾個(gè)值都分別打印出來,或者判斷其中某一個(gè)值上炎,超過1恃逻,說明線程數(shù)字超過1了,就是問題出現(xiàn)了藕施。

但是這個(gè)OS_dispatch_semaphore是底層c的數(shù)據(jù)結(jié)構(gòu)寇损,無法通過屬性直接獲取,或者通過結(jié)構(gòu)體->形式引用到其中的值裳食。 所以只能通過打印這個(gè)OS_dispatch_semaphore矛市,通過日志來分析(單步debug很難復(fù)現(xiàn)多線程問題)。
所以就添加了打印語句:

CMVoIPLogInfo(@"CMVoipMulticastDelegate signal:%@", signal);

結(jié)果如下:

<OS_dispatch_semaphore: semaphore[0x1c02892e0]>

可見其中并沒有{ xref = 1, ref = 1, port = 0x25351, value = 0, orig = 1 } 數(shù)據(jù)結(jié)構(gòu)中具體元素的值诲祸,難道是需要使用description 浊吏?
更改打印語句如下:

CMVoIPLogInfo(@"CMVoipMulticastDelegate signal:%@", signal.description);

結(jié)果如下:

<OS_dispatch_semaphore: semaphore[0x1c02892e0]>

怎么還是這樣!
按理說救氯,lldb的調(diào)試時(shí)的上下文和打印語句應(yīng)該是一樣的找田,應(yīng)該可以拿到同樣的數(shù)據(jù),為什么打印不出來同樣的值呢径密? 那我們就嘗試下是不是有其他屬性可以將斷點(diǎn)時(shí)的值復(fù)現(xiàn)出來午阵,當(dāng)我們隨便輸入一個(gè)字母d時(shí),可以看到聯(lián)想的屬性列表:

image.png

在description屬性下面還有一個(gè)debugDescription屬性享扔,嘗試一下結(jié)果如下:

<OS_dispatch_semaphore: semaphore[0x1c02892e0] = { xref = 1, ref = 1, port = 0x25351, value = 0, orig = 1 }>

原來debugDescription屬性時(shí)專門用來調(diào)試的底桂,iOS系統(tǒng)的對(duì)象數(shù)據(jù)結(jié)構(gòu)圖設(shè)計(jì)的還真是不錯(cuò),所以我們?nèi)粘i_發(fā)時(shí)惧眠,一些類或者model設(shè)計(jì)時(shí)籽懦,為了便于調(diào)試,也應(yīng)該分別重寫debugDescription和description兩個(gè)屬性氛魁。

這時(shí)暮顺,我們就可以在合適的地方添加log語句來看問題了厅篓,日志精簡如下:

2018-07-09 14:32:18:260 cMeeting[41681:4166517] CMVoipMulticastDelegate semaphore+1  before <OS_dispatch_semaphore: semaphore[0x1c02897e0] = { xref = 1, ref = 1, port = 0x25351, value = 0, orig = 1 }>
2018-07-09 14:32:18:260 cMeeting[41681:4166517] CMVoipMulticastDelegate semaphore+1  after  <OS_dispatch_semaphore: semaphore[0x1c02897e0] = { xref = 1, ref = 1, port = 0x25351, value = 1, orig = 1 }>
2018-07-09 14:32:18:358 cMeeting[41681:4166880] CMVoipMulticastDelegate semaphore-1 before  <OS_dispatch_semaphore: semaphore[0x1c02897e0] = { xref = 1, ref = 1, port = 0x25351, value = 2, orig = 1 }>

可以看到,value是當(dāng)前線程數(shù)捶码,當(dāng)出現(xiàn)大于1 的情況羽氮,就說明問題出現(xiàn)了(我們已經(jīng)設(shè)置了最大并發(fā)數(shù)是1)。
我們這時(shí)首先判斷是不是重復(fù)調(diào)用了dispatch_semaphore_signal方法惫恼,導(dǎo)致釋放了多余信號(hào)档押,例如釋放了兩個(gè)信號(hào)的話,就會(huì)導(dǎo)致兩個(gè)在等待dispatch_semaphore_wait的線程同時(shí)獲得信號(hào)祈纯,而進(jìn)入相關(guān)操作代碼中令宿,導(dǎo)致crash。
經(jīng)過代碼走查腕窥,和日志分析粒没,并沒有這種情況。

經(jīng)過日志和debug調(diào)試簇爆,我們發(fā)現(xiàn)在出現(xiàn)value = 1癞松,即已經(jīng)有一個(gè)線程獲得信號(hào)之后,其他線程在dispatch_semaphore_wait方法處沒有阻塞等待冕碟,而是直接開始執(zhí)行后面代碼拦惋,也就是說這里信號(hào)量確實(shí)沒有起到限制并發(fā)線程數(shù)目的作用。

那么問題在哪呢安寺?
是超時(shí)時(shí)間沒起作用魂奥? 還是信號(hào)量使用有問題仆潮?

經(jīng)過查閱其他人使用信號(hào)量的方式,驚人的發(fā)現(xiàn),overTime這里雖然要求是dispatch_time 類型跷叉,但是正確的卻是直接使用DISPATCH_TIME_FOREVER咱扣,而不是如上面所示的dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER);

查看dispatch的源碼也能判斷到這里的錯(cuò)誤所在:

dispatch_time_t
dispatch_time(dispatch_time_t inval, int64_t delta)
{
    if (inval == DISPATCH_TIME_FOREVER) {
        return DISPATCH_TIME_FOREVER;
    }
    if ((int64_t)inval < 0) {
        // wall clock
        if (delta >= 0) {
            if ((int64_t)(inval -= delta) >= 0) {
                return DISPATCH_TIME_FOREVER;      // overflow
            }
            return inval;
        }
        if ((int64_t)(inval -= delta) >= -1) {
            // -1 is special == DISPATCH_TIME_FOREVER == forever
            return -2;      // underflow
        }
        return inval;
    }
    // mach clock
    delta = _dispatch_time_nano2mach(delta);
    if (inval == 0) {
        inval = mach_absolute_time();
    }
    if (delta >= 0) {
        if ((int64_t)(inval += delta) <= 0) {
            return DISPATCH_TIME_FOREVER;      // overflow
        }
        return inval;
    }
    if ((int64_t)(inval += delta) < 1) {
        return 1;       // underflow
    }
    return inval;
}

這個(gè)函數(shù)邏輯比較簡單硝烂,當(dāng)我們錯(cuò)誤的使用dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER);這種形式時(shí)别威,對(duì)應(yīng)函數(shù)的入?yún)nval 和 delta分別是DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER,而二者宏定義的值為:

#define DISPATCH_TIME_NOW 0
#define DISPATCH_TIME_FOREVER (~0ull)

所以此函數(shù)就會(huì)經(jīng)下面邏輯將inval返回凳枝,

if (inval == 0) {
        inval = mach_absolute_time();
    }

也就是說返回的就是mach_absolute_time()抄沮,獲取到的是當(dāng)前系統(tǒng)的時(shí)間。 信號(hào)量在判斷返回的時(shí)間時(shí)判斷到此時(shí)刻岖瑰,發(fā)現(xiàn)已經(jīng)過去了叛买,那么直接就不等待而直接執(zhí)行下面的代碼,從而沒有實(shí)現(xiàn)阻塞的目的蹋订。

所以從這個(gè)函數(shù)我們也能看出率挣,我們正確的使用方式應(yīng)該是:
dispatch_time(DISPATCH_TIME_FOREVER, DISPATCH_TIME_NOW);
這樣會(huì)通過下面的邏輯:

if (inval == DISPATCH_TIME_FOREVER) {
        return DISPATCH_TIME_FOREVER;
    }

直接返回DISPATCH_TIME_FOREVER,從而得到了我們期望的值露戒。這里其實(shí)第二個(gè)參數(shù)就沒有實(shí)際作用了椒功。

總結(jié):

  1. 信號(hào)量直接打印%@捶箱,無法顯示已經(jīng)開啟的信號(hào)數(shù)量,因?yàn)榇藭r(shí)使用的是x.description动漾, 并且比xcode的debug斷點(diǎn)時(shí)看到的信息少丁屎,此時(shí)應(yīng)該使用x.debugDescription,此時(shí)的顯示信息即斷點(diǎn)可查看的信息相符

  2. 通過打印信號(hào)量谦炬,查看其中value值可以判斷信號(hào)數(shù)量悦屏,當(dāng)creat為1時(shí),最多value只能為1键思,如果有2的情況,說明未起作用

  3. 當(dāng)信號(hào)量未起作用甫贯,可以從兩方面考慮:
    超時(shí)時(shí)間設(shè)置錯(cuò)誤吼鳞,例如很短的超時(shí)時(shí)間,或者參數(shù)傳遞的是dispatch_time變量其實(shí)就是0.
    是否有重復(fù)signal的地方叫搁,即與wait不配對(duì)

  4. 信號(hào)量引起死鎖:當(dāng)后面進(jìn)來wait信號(hào)量的線程與前面已經(jīng)等到信號(hào)但還未signal的signal所處是同一個(gè)線程時(shí)赔桌,由于wait阻塞了線程,導(dǎo)致signal一直無法進(jìn)入渴逻,一直無法釋放疾党,wait死鎖在那。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惨奕,一起剝皮案震驚了整個(gè)濱河市雪位,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梨撞,老刑警劉巖雹洗,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卧波,居然都是意外死亡时肿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門港粱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來螃成,“玉大人,你說我怎么就攤上這事查坪〈绾辏” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵咪惠,是天一觀的道長击吱。 經(jīng)常有香客問我,道長遥昧,這世上最難降的妖魔是什么覆醇? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任朵纷,我火速辦了婚禮,結(jié)果婚禮上永脓,老公的妹妹穿的比我還像新娘袍辞。我一直安慰自己,他們只是感情好常摧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布搅吁。 她就那樣靜靜地躺著,像睡著了一般落午。 火紅的嫁衣襯著肌膚如雪谎懦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天溃斋,我揣著相機(jī)與錄音界拦,去河邊找鬼。 笑死梗劫,一個(gè)胖子當(dāng)著我的面吹牛享甸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梳侨,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛉威,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了走哺?” 一聲冷哼從身側(cè)響起蚯嫌,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎割坠,沒想到半個(gè)月后齐帚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡彼哼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年对妄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敢朱。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剪菱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拴签,到底是詐尸還是另有隱情孝常,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布蚓哩,位于F島的核電站构灸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏岸梨。R本人自食惡果不足惜喜颁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一稠氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧半开,春花似錦隔披、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纠永,卻和暖如春鬓长,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尝江。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工痢士, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茂装。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像善延,于是被迫代替她去往敵國和親少态。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Managing Units of Work(管理工作單位) 調(diào)度塊允許您直接配置隊(duì)列中各個(gè)工作單元的屬性易遣。它們還...
    edison0428閱讀 7,970評(píng)論 0 1
  • 本文為轉(zhuǎn)載資料彼妻,原文地址: http://www.reibang.com/p/02821f9d7777 一、信號(hào)量...
    一曰就是一天閱讀 1,135評(píng)論 0 3
  • 泰戈?duì)栒f過‘只管走下去幽邓,不必逗留著去采集鮮花去保存,因?yàn)橐宦飞匣鹇觯ǘ渥詴?huì)繼續(xù)開放的牵舵。’ 過了高三倦挂,人生也算...
    痞貓ii閱讀 755評(píng)論 4 2
  • 每次聲嘶力竭地看完一本書后 蕓落一般會(huì)幡然醒悟 發(fā)誓再也不看了 權(quán)當(dāng)最后一本 不過前段日子 似乎嗅到了靈魂腐朽的味...
    聊勝于無中閱讀 270評(píng)論 0 0
  • 內(nèi)部結(jié)構(gòu) 核心 回歸本質(zhì)畸颅,做好一個(gè)孵化器的基礎(chǔ)核心,和創(chuàng)業(yè)項(xiàng)目一樣方援,只有:人没炒。重視孵化器的運(yùn)營人員,設(shè)置合理的公司...
    梅晨斐閱讀 1,077評(píng)論 0 6