版本記錄
版本號 | 時間 |
---|---|
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á)到互斥,分別是P
和V
操作紧卒。 一般情況是這樣進(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ù)~~~