本文為轉載資料,原文地址: http://www.lxweimin.com/p/02821f9d7777
一、信號量的簡單介紹:
1.信號量:
信號量(Semaphore),有時被稱為信號燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被并發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量。為了完成這個過程,需要創建一個信號量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵代碼段的首末端。確認這些信號量VI引用的是初始創建的信號量。
2.特性:
抽象的來講,信號量的特性如下:信號量是一個非負整數(車位數),所有通過它的線程/進程(車輛)都會將該整數減一(通過它當然是為了使用資源),當該整數值為零時,所有試圖通過它的線程都將處于等待狀態。在信號量上我們定義兩種操作: Wait(等待) 和 Release(釋放)。當一個線程調用Wait操作時,它要么得到資源然后將信號量減一,要么一直等下去(指放入阻塞隊列),直到信號量大于等于一時。Release(釋放)實際上是在信號量上執行加操作,對應于車輛離開停車場,該操作之所以叫做“釋放”是因為釋放了由信號量守護的資源。
3.描述:
以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知后,打開車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。
在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用。
4.發展史:
1965年,荷蘭學者Edsger Dijkstra提出的信號量(Semaphores)機制是一種卓有成效的進程同步工具,在長期廣泛的應用中,信號量機制得到了極大的發展,它從整型信號量經記錄型信號量,進而發展成為“信號量集機制”,現在信號量機制已經被廣泛的應用到單處理機和多處理機系統以及計算機網絡中。
5.在IOS系統GCD的semaphore.h頭文件中提供三個方法進行PV操作
在IOS系統GCD的semaphore.h頭文件中提供三個方法進行PV操作// 這value是初始化多少個信號量1.dispatch_semaphore_create(longvalue);// 這個方法是P操作對信號量減一,dsema這個參數表示對哪個信號量進行減一,如果該信號量為0則等待,timeout這個參數則是傳入等待的時長。2.dispatch_semaphore_wait(dispatch_semaphore_tdsema,dispatch_time_ttimeout);// 這個方法是V操作對信號量加一,dsema這個參數表示對哪個信號量進行加一3.dispatch_semaphore_signal(dispatch_semaphore_tdsema);
二、下面是最簡單的例子,利用信號量或同步鎖對多線程操作iphoneNumber變量進行互斥。
////? ViewController.m//? semaphoreTest2////? Created by huangxianchao on 17/05/2017.//? Copyright ? 2017 黃先超. All rights reserved.//#import"ViewController.h"@interfaceViewController()/// iphone的數量@property(nonatomic,assign)intiphoneNumber;/// 互斥用的信號量@property(nonatomic,strong) dispatch_semaphore_t semaphore;@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];self.iphoneNumber =1000;// 初始化1個信號量self.semaphore = dispatch_semaphore_create(1);/// 通過信號量進行互斥,開啟三個窗口(線程)同時賣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";/// 通過同步鎖進行互斥,開啟三個窗口(線程)同時賣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];}/// 通過信號量達到互斥- (void)sellIphone{while(1) {// P操作對信號量進行減一,然后信號量變0,限制其他窗口(線程)進入dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);if(self.iphoneNumber >0)// 檢查還有沒iphone可賣{NSLog(@"賣出iphone剩下%d臺iphone",--self.iphoneNumber);? ? ? ? }else{NSLog(@"iphone沒有庫存了");return;? ? ? ? }// V操作對信號量進行加一,然后信號量為1,其他窗口(線程)就能進入了dispatch_semaphore_signal(self.semaphore);? ? }}/// 通過同步鎖進行互斥,通過同步鎖會比通過信號量控制的方式多進入該臨界代碼(線程數量-1)次- (void)sellIphoneWithSynchronization{while(1) {@synchronized(self) {if(self.iphoneNumber >0)// 檢查還有沒iphone可賣{NSLog(@"%@賣出iphone剩下%d臺iphone",[NSThreadcurrentThread].name,--self.iphoneNumber);? ? ? ? ? ? }else{NSLog(@"iphone沒有庫存了");return;? ? ? ? ? ? }? ? ? ? }? ? }}@end
注意:如果不理解可以注釋信號量代碼或者同步鎖代碼仔細觀察iphoneNumber值的變化
三、控制同一個信號量(臨界資源)下的相關線程最大的并發數
////? 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這個信號量的相關線程最多幾個線程并發運行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
下面是兩個現實生活的例子
一、這是關于公交車上司機和售票員的配合問題。
1.司機需要等待售票員關門后才能開車
2.售票員需要等待司機停車后才能開門
// 司機是否停車的信號量,dispatch_semaphore_t semaphoreStopBused = dispatch_semaphore_create(1);// 售票員是否關門的信號量dispatch_semaphore_t semaphoreCloseDoored = dispatch_semaphore_create(0);// 拿到全局隊列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 司機的相關操作dispatch_async(quene, ^{while(1) {// 司機等待售票員關門,DISPATCH_TIME_FOREVER表示這個信號量如果是0就一直等待這個信號量dispatch_semaphore_wait(semaphoreCloseDoored, DISPATCH_TIME_FOREVER);NSLog(@"司機開車");NSLog(@"司機停車");// 司機已關門,停車的信號量加一dispatch_semaphore_signal(semaphoreStopBused);? ? ? ? }? ? });// 售票員的相關操作dispatch_async(quene, ^{while(1) {// 售票員等待司機停車dispatch_semaphore_wait(semaphoreStopBused, DISPATCH_TIME_FOREVER);NSLog(@"售票員開門");NSLog(@"售票員關門");// 售票員已經關門,關門的信號量加一dispatch_semaphore_signal(semaphoreCloseDoored);? ? ? ? }? ? });
注意:
1.semaphoreStopBused和semaphoreCloseDoored信號量只能同時有一個是1,這值表示當前是停車了還是關門了,會觸發相關的流程
2.讓semaphoreCloseDoored為1,semaphoreStopBused為0試試,觀察最開始的流程走的順序有什么不同
二、生產和銷售的問題
/*
生產和銷售的線程同時操縱iphoneNumber變量為了保證數據的正確性,并且生產到5臺iphone就不再生產了。
*/// iphone數量__blockintiphoneNumber =0;// 生產和銷售的互斥dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 拿到全局隊列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 生產iphonedispatch_async(quene, ^{while(1) {// 生產和銷售的互斥,等待信號量dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber <5) {NSLog(@"生產iphone,目前有%d臺",++iphoneNumber);? ? ? ? ? ? }? ? ? ? ? ? dispatch_semaphore_signal(semaphore);? ? ? ? }? ? });// 銷售iphonedispatch_async(quene, ^{while(1) {// 生產和銷售的互斥,等待信號量dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber >0) {NSLog(@"賣掉一臺iphone,目前有%d臺",--iphoneNumber);? ? ? ? ? ? }? ? ? ? ? ? dispatch_semaphore_signal(semaphore);? ? ? ? }? ? });
22702FAC-1779-4C57-86A1-73C7E74A1121.png
上面圖的結果是正確的,生產和銷售后的數據都能對的上。
如果把代碼更改為下面的情況,就是不用信號量去做互斥的話,看下圖生成的錯誤結果圖
/*
生產和銷售的互斥保證得到的數據是正確的,并且生產到5臺iphone就不再生產了
*/// iphone數量__blockintiphoneNumber =0;// 生產和銷售的互斥//? ? dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);// 拿到全局隊列dispatch_queue_tquene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);// 生產iphonedispatch_async(quene, ^{while(1) {// 生產和銷售的互斥,等待信號量//? ? ? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber <5) {NSLog(@"生產iphone,目前有%d臺",++iphoneNumber);? ? ? ? ? ? }//? ? ? ? ? ? dispatch_semaphore_signal(semaphore);}? ? });// 銷售iphonedispatch_async(quene, ^{while(1) {// 生產和銷售的互斥,等待信號量//? ? ? ? ? ? dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if(iphoneNumber >0) {NSLog(@"賣掉一臺iphone,目前有%d臺",--iphoneNumber);? ? ? ? ? ? }//? ? ? ? ? ? dispatch_semaphore_signal(semaphore);}? ? });
看下圖生成的錯誤結果圖
圖片.png
上圖的結果有兩處錯誤
1.連續生產兩次iphone后產品的數量都是5。
2.連續賣掉兩次iphone后產品的數量都是4。
參考資料: