本文為轉(zhuǎn)載資料案站,原文地址: http://www.reibang.com/p/02821f9d7777
一侄柔、信號(hào)量的簡(jiǎn)單介紹:
1.信號(hào)量:
信號(hào)量(Semaphore)浑槽,有時(shí)被稱(chēng)為信號(hào)燈巫员,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來(lái)保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用四敞。在進(jìn)入一個(gè)關(guān)鍵代碼段之前勾缭,線程必須獲取一個(gè)信號(hào)量;一旦該關(guān)鍵代碼段完成了目养,那么該線程必須釋放信號(hào)量。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待直到第一個(gè)線程釋放信號(hào)量毒嫡。為了完成這個(gè)過(guò)程癌蚁,需要?jiǎng)?chuàng)建一個(gè)信號(hào)量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個(gè)關(guān)鍵代碼段的首末端兜畸。確認(rèn)這些信號(hào)量VI引用的是初始創(chuàng)建的信號(hào)量努释。
2.特性:
抽象的來(lái)講,信號(hào)量的特性如下:信號(hào)量是一個(gè)非負(fù)整數(shù)(車(chē)位數(shù))咬摇,所有通過(guò)它的線程/進(jìn)程(車(chē)輛)都會(huì)將該整數(shù)減一(通過(guò)它當(dāng)然是為了使用資源)伐蒂,當(dāng)該整數(shù)值為零時(shí),所有試圖通過(guò)它的線程都將處于等待狀態(tài)肛鹏。在信號(hào)量上我們定義兩種操作: Wait(等待) 和 Release(釋放)逸邦。當(dāng)一個(gè)線程調(diào)用Wait操作時(shí),它要么得到資源然后將信號(hào)量減一在扰,要么一直等下去(指放入阻塞隊(duì)列)缕减,直到信號(hào)量大于等于一時(shí)。Release(釋放)實(shí)際上是在信號(hào)量上執(zhí)行加操作芒珠,對(duì)應(yīng)于車(chē)輛離開(kāi)停車(chē)場(chǎng)桥狡,該操作之所以叫做“釋放”是因?yàn)獒尫帕擞尚盘?hào)量守護(hù)的資源。
3.描述:
以一個(gè)停車(chē)場(chǎng)的運(yùn)作為例皱卓。簡(jiǎn)單起見(jiàn)裹芝,假設(shè)停車(chē)場(chǎng)只有三個(gè)車(chē)位,一開(kāi)始三個(gè)車(chē)位都是空的娜汁。這時(shí)如果同時(shí)來(lái)了五輛車(chē)嫂易,看門(mén)人允許其中三輛直接進(jìn)入,然后放下車(chē)攔掐禁,剩下的車(chē)則必須在入口等待炬搭,此后來(lái)的車(chē)也都不得不在入口處等待蜈漓。這時(shí),有一輛車(chē)離開(kāi)停車(chē)場(chǎng)宫盔,看門(mén)人得知后融虽,打開(kāi)車(chē)攔,放入外面的一輛進(jìn)去灼芭,如果又離開(kāi)兩輛有额,則又可以放入兩輛,如此往復(fù)彼绷。
在這個(gè)停車(chē)場(chǎng)系統(tǒng)中巍佑,車(chē)位是公共資源,每輛車(chē)好比一個(gè)線程寄悯,看門(mén)人起的就是信號(hào)量的作用萤衰。
4.發(fā)展史:
1965年,荷蘭學(xué)者Edsger Dijkstra提出的信號(hào)量(Semaphores)機(jī)制是一種卓有成效的進(jìn)程同步工具猜旬,在長(zhǎng)期廣泛的應(yīng)用中脆栋,信號(hào)量機(jī)制得到了極大的發(fā)展,它從整型信號(hào)量經(jīng)記錄型信號(hào)量洒擦,進(jìn)而發(fā)展成為“信號(hào)量集機(jī)制”椿争,現(xiàn)在信號(hào)量機(jī)制已經(jīng)被廣泛的應(yīng)用到單處理機(jī)和多處理機(jī)系統(tǒng)以及計(jì)算機(jī)網(wǎng)絡(luò)中。
5.在IOS系統(tǒng)GCD的semaphore.h頭文件中提供三個(gè)方法進(jìn)行PV操作
在IOS系統(tǒng)GCD的semaphore.h頭文件中提供三個(gè)方法進(jìn)行PV操作// 這value是初始化多少個(gè)信號(hào)量1.dispatch_semaphore_create(longvalue);// 這個(gè)方法是P操作對(duì)信號(hào)量減一熟嫩,dsema這個(gè)參數(shù)表示對(duì)哪個(gè)信號(hào)量進(jìn)行減一秦踪,如果該信號(hào)量為0則等待,timeout這個(gè)參數(shù)則是傳入等待的時(shí)長(zhǎng)掸茅。2.dispatch_semaphore_wait(dispatch_semaphore_tdsema,dispatch_time_ttimeout);// 這個(gè)方法是V操作對(duì)信號(hào)量加一椅邓,dsema這個(gè)參數(shù)表示對(duì)哪個(gè)信號(hào)量進(jìn)行加一3.dispatch_semaphore_signal(dispatch_semaphore_tdsema);
二、下面是最簡(jiǎn)單的例子昧狮,利用信號(hào)量或同步鎖對(duì)多線程操作iphoneNumber變量進(jìn)行互斥希坚。
////? ViewController.m//? semaphoreTest2////? Created by huangxianchao on 17/05/2017.//? Copyright ? 2017 黃先超. All rights reserved.//#import"ViewController.h"@interfaceViewController()/// iphone的數(shù)量@property(nonatomic,assign)intiphoneNumber;/// 互斥用的信號(hào)量@property(nonatomic,strong) dispatch_semaphore_t semaphore;@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];self.iphoneNumber =1000;// 初始化1個(gè)信號(hào)量self.semaphore = dispatch_semaphore_create(1);/// 通過(guò)信號(hào)量進(jìn)行互斥,開(kāi)啟三個(gè)窗口(線程)同時(shí)賣(mài)iphoneNSThread*thread1 = [[NSThreadalloc] initWithTarget:selfselector:@selector(sellIphone) object:nil];? ? thread1.name =@"窗口1";NSThread*thread2 = [[NSThreadalloc] initWithTarget:selfselector:@selector(sellIphone) object:nil];? ? thread2.name =@"窗口2";NSThread*thread3 = [[NSThreadalloc] initWithTarget:selfselector:@selector(sellIphone) object:nil];? ? thread3.name =@"窗口3";/// 通過(guò)同步鎖進(jìn)行互斥陵且,開(kāi)啟三個(gè)窗口(線程)同時(shí)賣(mài)iphone//? ? NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellIphoneWithSynchronization) object:nil];//? ? thread1.name = @"窗口1";//? ? NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellIphoneWithSynchronization) object:nil];//? ? thread2.name = @"窗口2";//? ? NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellIphoneWithSynchronization) object:nil];//? ? thread3.name = @"窗口3";[thread1 start];? ? [thread2 start];? ? [thread3 start];}/// 通過(guò)信號(hào)量達(dá)到互斥- (void)sellIphone{while(1) {// P操作對(duì)信號(hào)量進(jìn)行減一裁僧,然后信號(hào)量變0,限制其他窗口(線程)進(jìn)入dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);if(self.iphoneNumber >0)// 檢查還有沒(méi)iphone可賣(mài){NSLog(@"賣(mài)出iphone剩下%d臺(tái)iphone",--self.iphoneNumber);? ? ? ? }else{NSLog(@"iphone沒(méi)有庫(kù)存了");return;? ? ? ? }// V操作對(duì)信號(hào)量進(jìn)行加一慕购,然后信號(hào)量為1聊疲,其他窗口(線程)就能進(jìn)入了dispatch_semaphore_signal(self.semaphore);? ? }}/// 通過(guò)同步鎖進(jìn)行互斥,通過(guò)同步鎖會(huì)比通過(guò)信號(hào)量控制的方式多進(jìn)入該臨界代碼(線程數(shù)量-1)次- (void)sellIphoneWithSynchronization{while(1) {@synchronized(self) {if(self.iphoneNumber >0)// 檢查還有沒(méi)iphone可賣(mài){NSLog(@"%@賣(mài)出iphone剩下%d臺(tái)iphone",[NSThreadcurrentThread].name,--self.iphoneNumber);? ? ? ? ? ? }else{NSLog(@"iphone沒(méi)有庫(kù)存了");return;? ? ? ? ? ? }? ? ? ? }? ? }}@end
注意:如果不理解可以注釋信號(hào)量代碼或者同步鎖代碼仔細(xì)觀察iphoneNumber值的變化
三沪悲、控制同一個(gè)信號(hào)量(臨界資源)下的相關(guān)線程最大的并發(fā)數(shù)
////? ViewController.m//? samephoreTest3////? Created by huangxianchao on 17/05/2017.//? Copyright ? 2017 黃先超. All rights reserved.//#import"ViewController.h"@interfaceViewController()@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];//這里的value控制基于semaphore這個(gè)信號(hào)量的相關(guān)線程最多幾個(gè)線程并發(fā)運(yùn)行dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);//線程1dispatch_async(quene, ^{? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"thread1 start");? ? ? ? dispatch_semaphore_signal(semaphore);NSLog(@"thread1 finish");? ? });//線程2dispatch_async(quene, ^{? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"thread2 start");? ? ? ? dispatch_semaphore_signal(semaphore);NSLog(@"thread2 finish");? ? });//線程3dispatch_async(quene, ^{? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"thread3 start");? ? ? ? dispatch_semaphore_signal(semaphore);NSLog(@"thread3 finish");? ? });}@end
下面是兩個(gè)現(xiàn)實(shí)生活的例子
一获洲、這是關(guān)于公交車(chē)上司機(jī)和售票員的配合問(wèn)題。
1.司機(jī)需要等待售票員關(guān)門(mén)后才能開(kāi)車(chē)
2.售票員需要等待司機(jī)停車(chē)后才能開(kāi)門(mén)
// 司機(jī)是否停車(chē)的信號(hào)量殿如,dispatch_semaphore_t semaphoreStopBused = dispatch_semaphore_create(1);// 售票員是否關(guān)門(mén)的信號(hào)量dispatch_semaphore_t semaphoreCloseDoored = dispatch_semaphore_create(0);// 拿到全局隊(duì)列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 司機(jī)的相關(guān)操作dispatch_async(quene, ^{while(1) {// 司機(jī)等待售票員關(guān)門(mén),DISPATCH_TIME_FOREVER表示這個(gè)信號(hào)量如果是0就一直等待這個(gè)信號(hào)量dispatch_semaphore_wait(semaphoreCloseDoored, DISPATCH_TIME_FOREVER);NSLog(@"司機(jī)開(kāi)車(chē)");NSLog(@"司機(jī)停車(chē)");// 司機(jī)已關(guān)門(mén)贡珊,停車(chē)的信號(hào)量加一dispatch_semaphore_signal(semaphoreStopBused);? ? ? ? }? ? });// 售票員的相關(guān)操作dispatch_async(quene, ^{while(1) {// 售票員等待司機(jī)停車(chē)dispatch_semaphore_wait(semaphoreStopBused, DISPATCH_TIME_FOREVER);NSLog(@"售票員開(kāi)門(mén)");NSLog(@"售票員關(guān)門(mén)");// 售票員已經(jīng)關(guān)門(mén)最爬,關(guān)門(mén)的信號(hào)量加一dispatch_semaphore_signal(semaphoreCloseDoored);? ? ? ? }? ? });
注意:
1.semaphoreStopBused和semaphoreCloseDoored信號(hào)量只能同時(shí)有一個(gè)是1,這值表示當(dāng)前是停車(chē)了還是關(guān)門(mén)了门岔,會(huì)觸發(fā)相關(guān)的流程
2.讓semaphoreCloseDoored為1爱致,semaphoreStopBused為0試試,觀察最開(kāi)始的流程走的順序有什么不同
二寒随、生產(chǎn)和銷(xiāo)售的問(wèn)題
/*
生產(chǎn)和銷(xiāo)售的線程同時(shí)操縱iphoneNumber變量為了保證數(shù)據(jù)的正確性糠悯,并且生產(chǎn)到5臺(tái)iphone就不再生產(chǎn)了。
*/// iphone數(shù)量__blockintiphoneNumber =0;// 生產(chǎn)和銷(xiāo)售的互斥dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 拿到全局隊(duì)列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 生產(chǎn)iphonedispatch_async(quene, ^{while(1) {// 生產(chǎn)和銷(xiāo)售的互斥,等待信號(hào)量dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber <5) {NSLog(@"生產(chǎn)iphone,目前有%d臺(tái)",++iphoneNumber);? ? ? ? ? ? }? ? ? ? ? ? dispatch_semaphore_signal(semaphore);? ? ? ? }? ? });// 銷(xiāo)售iphonedispatch_async(quene, ^{while(1) {// 生產(chǎn)和銷(xiāo)售的互斥,等待信號(hào)量dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber >0) {NSLog(@"賣(mài)掉一臺(tái)iphone,目前有%d臺(tái)",--iphoneNumber);? ? ? ? ? ? }? ? ? ? ? ? dispatch_semaphore_signal(semaphore);? ? ? ? }? ? });
22702FAC-1779-4C57-86A1-73C7E74A1121.png
上面圖的結(jié)果是正確的妻往,生產(chǎn)和銷(xiāo)售后的數(shù)據(jù)都能對(duì)的上栈幸。
如果把代碼更改為下面的情況瘫镇,就是不用信號(hào)量去做互斥的話,看下圖生成的錯(cuò)誤結(jié)果圖
/*
生產(chǎn)和銷(xiāo)售的互斥保證得到的數(shù)據(jù)是正確的宇弛,并且生產(chǎn)到5臺(tái)iphone就不再生產(chǎn)了
*/// iphone數(shù)量__blockintiphoneNumber =0;// 生產(chǎn)和銷(xiāo)售的互斥//? ? dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 拿到全局隊(duì)列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 生產(chǎn)iphonedispatch_async(quene, ^{while(1) {// 生產(chǎn)和銷(xiāo)售的互斥,等待信號(hào)量//? ? ? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber <5) {NSLog(@"生產(chǎn)iphone,目前有%d臺(tái)",++iphoneNumber);? ? ? ? ? ? }//? ? ? ? ? ? dispatch_semaphore_signal(semaphore);}? ? });// 銷(xiāo)售iphonedispatch_async(quene, ^{while(1) {// 生產(chǎn)和銷(xiāo)售的互斥,等待信號(hào)量//? ? ? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber >0) {NSLog(@"賣(mài)掉一臺(tái)iphone,目前有%d臺(tái)",--iphoneNumber);? ? ? ? ? ? }//? ? ? ? ? ? dispatch_semaphore_signal(semaphore);}? ? });
看下圖生成的錯(cuò)誤結(jié)果圖
圖片.png
上圖的結(jié)果有兩處錯(cuò)誤
1.連續(xù)生產(chǎn)兩次iphone后產(chǎn)品的數(shù)量都是5默刚。
2.連續(xù)賣(mài)掉兩次iphone后產(chǎn)品的數(shù)量都是4翻具。
參考資料: