iOS與多線程(一) —— GCD中的信號量及幾個重要函數(shù)

版本記錄

版本號 時間
V1.0 2017.08.15

前言

信號量機制是多線程通信中的比較重要的一部分诬乞,對于NSOperation可以設(shè)置并發(fā)數(shù)适袜,但是對于GCD就不能設(shè)置并發(fā)數(shù)了,那么就只能靠信號量機制了凯力。接下來這幾篇就會詳細(xì)的說一下并發(fā)機制盼产。

基本概念

信號量

信號量其實就是一個整數(shù)值并具有一個初始化的值饵婆,有關(guān)信號量有兩個操作:

  • 信號通知
  • 信號等待

這里我們需要注意:

  • 當(dāng)信號量被信號通知時,其計數(shù)會被增加辆飘。
  • 當(dāng)線程在信號量等待時啦辐,線程會阻塞,計數(shù)會減少蜈项。

GCD中的信號量

信號量控制互斥的原理

信號量就是一個資源計數(shù)器芹关,對信號量有兩個操作來達(dá)到互斥,分別是PV操作紧卒。 一般情況是這樣進(jìn)行臨界訪問或互斥訪問的: 設(shè)信號量值為1侥衬, 當(dāng)一個進(jìn)程1運行是,使用資源跑芳,進(jìn)行P操作轴总,即對信號量值減1,也就是資源數(shù)少了1個博个。這是信號量值為0怀樟。系統(tǒng)中規(guī)定當(dāng)信號量值為0是,必須等待盆佣,知道信號量值不為零才能繼續(xù)操作往堡。 這時如果進(jìn)程2想要運行,那么也必須進(jìn)行P操作共耍,但是此時信號量為0虑灰,所以無法減1,即不能P操作痹兜,也就阻塞穆咐。這樣就到到了進(jìn)程1排他訪問。 當(dāng)進(jìn)程1運行結(jié)束后,釋放資源对湃,進(jìn)行V操作崖叫。資源數(shù)重新加1,這是信號量的值變?yōu)?熟尉, 這時進(jìn)程2發(fā)現(xiàn)資源數(shù)不為0归露,信號量能進(jìn)行P操作了,立即執(zhí)行P操作斤儿。信號量值又變?yōu)?,次數(shù)進(jìn)程2咱有資源恐锦,排他訪問資源往果,這就是信號量來控制互斥的原理。

總的來說一铅,信號量為0時就阻塞線程陕贮,大于0就不會阻塞,通過改變信號量的值控制線程的阻塞潘飘,達(dá)到線程的同步肮之。

三種重要的函數(shù)

GCD中的信號量有三種操作函數(shù):

  • dispatch_semaphore_create: 創(chuàng)建一個信號量,具有整形的數(shù)值卜录,即為信號的總量戈擒。
  • dispatch_semaphore_signal
    • 返回值為long類型,當(dāng)返回值為0時艰毒,表示當(dāng)前并沒有線程等待其處理的信號量筐高,其處理的信號總量增加1。
    • 當(dāng)返回值不為0時丑瞧,表示其當(dāng)前有一個或者多個線程等待其處理的信號量柑土,并且該函數(shù)喚醒了一個等待的線程(當(dāng)線程有優(yōu)先級的時候,喚醒優(yōu)先級最高的線程绊汹,否則隨機喚醒)稽屏。
  • dispatch_semaphore_wait
    • 等待信號,具體操作是首先判斷信號量desema是否大于0西乖,如果大于0就減掉1個信號狐榔,往下執(zhí)行;
    • 如果等于0函數(shù)就阻塞該線程等待timeout(注意timeout類型為dispatch_time_t)時浴栽,其所處線程自動執(zhí)行其后的語句荒叼。

下面我們就詳細(xì)的說一下這幾個函數(shù)。

1. dispatch_semaphore_create
/*!
 * @function dispatch_semaphore_create
 *
 * @abstract
 * // 創(chuàng)建具有初始值的新計數(shù)信號量典鸡。
 * Creates new counting semaphore with an initial value.
 *
 * @discussion
 * Passing zero for the value is useful for when two threads need to reconcile
 * the completion of a particular event. Passing a value greater than zero is
 * useful for managing a finite pool of resources, where the pool size is equal
 * to the value.
 * //如果兩個線程需要調(diào)整特定事件的完成被廓,則給該值傳遞0。 傳遞大于零的值對于管理有限的資源池很有用萝玷,其中池大小等于該值嫁乘。
 *
 * @param value
 * The starting value for the semaphore. Passing a value less than zero will
 * cause NULL to be returned.
 * //信號的初始值昆婿,傳遞小于0的值會返回NULL
 * 
 * @result
 * The newly created semaphore, or NULL on failure.
 * //返回新創(chuàng)建的信號量,或者失敗時返回NULL
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW

dispatch_semaphore_t
dispatch_semaphore_create(long value);
2. dispatch_semaphore_signal
/*!
 * @function dispatch_semaphore_signal
 *
 * @abstract
 * Signal (increment) a semaphore.
 *  //信號量增加
 *
 * @discussion
 * Increment the counting semaphore. If the previous value was less than zero,
 * this function wakes a waiting thread before returning.
 *//增加計數(shù)信號量蜓斧。 如果以前的值小于零仓蛆,則此函數(shù)在返回之前喚醒等待線程。
 *
 * @param dsema The counting semaphore.
 * The result of passing NULL in this parameter is undefined.
 *// 該參數(shù)傳遞NULL時挎春,返回的結(jié)果未定義看疙。
 *
 * @result
 * This function returns non-zero if a thread is woken. Otherwise, zero is
 * returned.
 *//如果線程被喚醒,該函數(shù)返回的是個非0數(shù)直奋,否則能庆,返回的是0
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
3. dispatch_semaphore_wait
/*!
 * @function dispatch_semaphore_wait
 *
 * @abstract
 * Wait (decrement) for a semaphore.
 *//等待(遞減)一個信號量
 *
 * @discussion
 * Decrement the counting semaphore. If the resulting value is less than zero,
 * this function waits for a signal to occur before returning.
 *//減少計數(shù)信號量。 如果結(jié)果值小于零脚线,則此函數(shù)在返回之前等待信號發(fā)生搁胆。
 *
 * @param dsema
 * The semaphore. The result of passing NULL in this parameter is undefined.
 *//信號量,給這個參數(shù)傳遞NULL的結(jié)果沒有定義邮绿。
 *
 * @param timeout
 * When to timeout (see dispatch_time). As a convenience, there are the
 * DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
 *//超時(請參閱dispatch_time)的時候渠旁, 為方便起見,有DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量船逮。
 *
 * @result
 * Returns zero on success, or non-zero if the timeout occurred.
 *//成功后返回0顾腊,超時發(fā)生的時候返回非0數(shù)。
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

信號量的一種形象比喻

下面就以停車做例子說明信號量這幾個函數(shù)的使用傻唾。

  • 停車場剩余4個車位投慈,那么即使同時來了四輛車也能停的下。如果此時來了五輛車冠骄,那么就有一輛需要等待伪煤。

  • 信號量的值就相當(dāng)于剩余車位的數(shù)目,
    dispatch_semaphore_wait函數(shù)就相當(dāng)于來了一輛車凛辣,dispatch_semaphore_signal就相當(dāng)于走了一輛車抱既。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了dispatch_semaphore_create(long value)

  • 調(diào)用一次dispatch_semaphore_signal扁誓,剩余的車位就增加一個防泵;調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個;

  • 當(dāng)剩余車位為0時蝗敢,再來車(即調(diào)用dispatch_semaphore_wait)就只能等待捷泞。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心寿谴,給自己設(shè)定了一段等待時間锁右,這段時間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就像把車停在這咏瑟,所以就一直等下去拂到。

幾個簡單的例子

例1

下面看一個簡單的使用例子。

#import "JJGCDSemaphoreVC.h"

@interface JJGCDSemaphoreVC ()

@end

@implementation JJGCDSemaphoreVC

#pragma mark - OVerride Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    //創(chuàng)建一個為1信號量的信號
    // 打印輸出:<OS_dispatch_semaphore: semaphore[0x174099b40] = { xrefcnt = 0x1, refcnt = 0x1, port = 0x0, value = 1, orig = 1 }>
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    
    __block long x = 0;
    NSLog(@"0_x:%ld",x);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        NSLog(@"waiting");
        
        //此時信號量為0 對signal增加1 信號量變?yōu)?码泞,
        x = dispatch_semaphore_signal(signal);
        NSLog(@"1_x:%ld",x);
        
        sleep(2);
        NSLog(@"waking");
        
        x = dispatch_semaphore_signal(signal);
        NSLog(@"2_x:%ld",x);
    });
    
    //此時信號量為1 所以執(zhí)行下邊兄旬,對signal減掉1,然后信號量為0
    x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSLog(@"3_x:%ld",x);
    
    //此時信號量為0余寥,永遠(yuǎn)等待领铐,在等待的時候執(zhí)行block了,在等待block時候block內(nèi)對信號量增加了1劈狐,然后開始執(zhí)行下邊罐孝,并且信號量再次減掉1 變?yōu)?
    x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSLog(@"wait 2");
    NSLog(@"4_x:%ld",x);
    
    //此時信號量為0,永遠(yuǎn)等待肥缔,在等待的時候執(zhí)行block了,在等待block時候block內(nèi)對信號量增加了1汹来,然后開始執(zhí)行下邊续膳,并且信號量再次減掉1 變?yōu)?
    x = dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    
    NSLog(@"wait 3");
    NSLog(@"5_x:%ld",x);
    
    sleep(2);
    
    x = dispatch_semaphore_signal(signal);
    NSLog(@"6_x:%ld",x);
}

@end

下面看輸出結(jié)果,這里要注意的是調(diào)用順序和信號量的值收班。

2017-08-16 14:46:15.126895+0800 JJOC[10085:4483826] 0_x:0
2017-08-16 14:46:15.126978+0800 JJOC[10085:4483826] 3_x:0
2017-08-16 14:46:16.128546+0800 JJOC[10085:4483865] waiting
2017-08-16 14:46:16.128746+0800 JJOC[10085:4483826] wait 2
2017-08-16 14:46:16.128803+0800 JJOC[10085:4483826] 4_x:0
2017-08-16 14:46:16.128870+0800 JJOC[10085:4483865] 1_x:1
2017-08-16 14:46:18.132708+0800 JJOC[10085:4483865] waking
2017-08-16 14:46:18.132878+0800 JJOC[10085:4483826] wait 3
2017-08-16 14:46:18.132959+0800 JJOC[10085:4483826] 5_x:0
2017-08-16 14:46:18.133062+0800 JJOC[10085:4483865] 2_x:1
2017-08-16 14:46:20.134107+0800 JJOC[10085:4483826] 6_x:0

例2

#import "JJGCDSemaphoreVC.h"

@interface JJGCDSemaphoreVC ()

@end

@implementation JJGCDSemaphoreVC

#pragma mark - OVerride Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self demo2];
}

#pragma mark - Object Private Function

- (void)demo2
{
    // 創(chuàng)建隊列組
    dispatch_group_t group = dispatch_group_create();
    
    // 創(chuàng)建信號量坟岔,并且設(shè)置值為10
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    for (int i = 0; i < 100; i++)
    {
        // 由于是異步執(zhí)行的,所以每次循環(huán)Block里面的dispatch_semaphore_signal根本還沒有執(zhí)行就會執(zhí)行dispatch_semaphore_wait摔桦,
        //從而semaphore-1.當(dāng)循環(huán)10次后社付,semaphore等于0,則會阻塞線程邻耕,直到執(zhí)行了Block的dispatch_semaphore_signal 才會繼續(xù)執(zhí)行
        NSInteger value = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"1_%ld",value);
        
        dispatch_group_async(group, queue, ^{
            NSLog(@"%i",i);
            sleep(3);
            // 每次發(fā)送信號則semaphore會+1鸥咖,
            NSInteger value = dispatch_semaphore_signal(semaphore);
            NSLog(@"2_%ld",value);
        });
    }
}
@end

下面看一部分輸出結(jié)果

2017-08-16 15:15:16.975989+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:15:19.638091+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:21.910431+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:23.324425+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:24.651183+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:26.033626+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:45.187604+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:46.250799+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:47.151037+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:48.017023+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:16:52.117765+0800 JJOC[10107:4488216] 1
2017-08-16 15:16:52.118002+0800 JJOC[10107:4488217] 0
2017-08-16 15:20:20.225583+0800 JJOC[10107:4488384] 2
2017-08-16 15:20:20.225801+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:20:26.887084+0800 JJOC[10107:4488216] 2_1
2017-08-16 15:22:02.490991+0800 JJOC[10107:4488216] 3
2017-08-16 15:22:02.491183+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:22:05.175372+0800 JJOC[10107:4488217] 2_1
2017-08-16 15:22:06.512384+0800 JJOC[10107:4488217] 4
2017-08-16 15:22:06.512568+0800 JJOC[10107:4488195] 1_0
2017-08-16 15:22:08.819089+0800 JJOC[10107:4488216] 2_1
2017-08-16 15:22:09.809285+0800 JJOC[10107:4488216] 5
2017-08-16 15:22:09.809472+0800 JJOC[10107:4488195] 1_0
(lldb) 

大家分析一下輸出結(jié)果就可以看到線程信息的同步。

參考文獻(xiàn)

1. iOS開發(fā)系列-信號量
2. 關(guān)于dispatch_semaphore的使用
3. 淺談GCD中的信號量

后記

未完兄世,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啼辣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子御滩,更是在濱河造成了極大的恐慌鸥拧,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件削解,死亡現(xiàn)場離奇詭異富弦,居然都是意外死亡,警方通過查閱死者的電腦和手機氛驮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門腕柜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事媳握〖钇ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵蛾找,是天一觀的道長娩脾。 經(jīng)常有香客問我,道長打毛,這世上最難降的妖魔是什么柿赊? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮幻枉,結(jié)果婚禮上碰声,老公的妹妹穿的比我還像新娘。我一直安慰自己熬甫,他們只是感情好胰挑,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椿肩,像睡著了一般瞻颂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上郑象,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天贡这,我揣著相機與錄音,去河邊找鬼厂榛。 笑死盖矫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的击奶。 我是一名探鬼主播辈双,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼正歼!你這毒婦竟也來了辐马?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤局义,失蹤者是張志新(化名)和其女友劉穎喜爷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萄唇,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡檩帐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了另萤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湃密。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡诅挑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泛源,到底是詐尸還是另有隱情拔妥,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布达箍,位于F島的核電站没龙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缎玫。R本人自食惡果不足惜硬纤,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赃磨。 院中可真熱鬧筝家,春花似錦、人聲如沸邻辉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽值骇。三九已至在扰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雷客,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工桥狡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搅裙,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓裹芝,卻偏偏與公主長得像部逮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嫂易,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

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