iOS 線程同步 資源搶奪
- 線程同步: 多線程開發(fā)保證公共訪問的資源不被同時訪問.設計到線程安全,一個好的設計是最好的保護. 在線程交互的的情況下根據(jù)你操作的資源類型選擇合適的方式是必要的.
原子操作
- atomic,處理簡單的數(shù)據(jù)類型.它不妨礙競爭線程.對于簡單的數(shù)據(jù)操作比如遞增一個計數(shù)器,原子操作比使用鎖具有跟高的性能,如上圖
POSIX 互斥鎖: pthread_mutex_t
鎖創(chuàng)建
- 有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動態(tài)方式行冰。
-
POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態(tài)初始化互斥鎖途乃,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads實現(xiàn)中腿时,pthread_mutex_t是一個結構悠栓,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。
-
動態(tài)方式是采用pthread_mutex_init()函數(shù)來初始化互斥鎖刀崖,API定義如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
其中mutexattr用于指定互斥鎖屬性(見下)颖榜,如果為NULL則使用缺省屬性棚饵。
PTHREAD_MUTEX_TIMED_NP
這是缺省值,也就是普通鎖朱转。當一個線程加鎖以后蟹地,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優(yōu)先級獲得鎖藤为。這種鎖策略保證了資源分配的公平性怪与。
PTHREAD_MUTEX_RECURSIVE_NP
嵌套鎖,允許同一個線程對同一個鎖成功獲得多次缅疟,并通過多次unlock解鎖分别。如果是不同線程請求,則在加鎖線程解鎖時重新競爭存淫。
PTHREAD_MUTEX_ERRORCHECK_NP
檢錯鎖耘斩,如果同一個線程請求同一個鎖,則返回EDEADLK桅咆,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同括授。這樣就保證當不允許多次加鎖時不會出現(xiàn)最簡單情況下的死鎖。
PTHREAD_MUTEX_ADAPTIVE_NP
適應鎖,動作最簡單的鎖類型荚虚,僅等待解鎖后重新競爭薛夜。
-
鎖操作
鎖操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個版述,不論哪種類型的鎖梯澜,都不可能被兩個不同的線程同時得到,而必須等待解鎖渴析。
對于普通鎖和適應鎖類型晚伙,解鎖者可以是同進程內任何線程;而檢錯鎖則必須由加鎖者解鎖才有效俭茧,否則返回EPERM咆疗;對于嵌套鎖,文檔和實現(xiàn)要求必須由加鎖者解鎖恢恼,但實驗結果表明并沒有這種限制民傻,這個不同目前還沒有得到解釋
在同一進程中的線程胰默,如果加鎖后沒有解鎖场斑,則任何其他線程都無法再獲得鎖
-
int pthread_mutex_lock(pthread_mutex_t *mutex) int pthread_mutex_unlock(pthread_mutex_t *mutex) int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經(jīng)被占據(jù)時返回EBUSY而不是掛起等待牵署。
-
pthread_mutex_destroy ()用于注銷一個互斥鎖漏隐,API定義如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
銷毀一個互斥鎖即意味著釋放它所占用的資源,且要求鎖當前處于開放狀態(tài)奴迅。由于在Linux中青责,互斥鎖并不占用任何資源,因此LinuxThreads中的 pthread_mutex_destroy()除了檢查鎖狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒有其他動作
while (1) { pthread_mutex_lock(&mutex); // 先取出總數(shù) NSInteger count = self.ticketCount; if (count > 0) { self.ticketCount = count - 1; NSLog(@"%@賣了一張票取具,還剩下%zd張", [NSThread currentThread].name, self.ticketCount); } else { NSLog(@"票已經(jīng)賣完了"); pthread_mutex_destroy(&mutex); break; } pthread_mutex_unlock(&mutex); }
NSLock類
在Cocoa程序中NSLock中實現(xiàn)了一個簡單的互斥鎖脖隶。所有鎖(包括NSLock)的接口實際上都是通過NSLocking協(xié)議定義的,它定義了lock和unlock方法暇检。你使用這些方法來獲取和釋放該鎖产阱。
-
除了標準的鎖行為,NSLock類還增加了tryLock和lockBeforeDate:方法块仆。方法tryLock試圖獲取一個鎖构蹬,但是如果鎖不可用的時候,它不會阻塞線程悔据。相反庄敛,它只是返回NO。而lockBeforeDate:方法試圖獲取一個鎖科汗,但是如果鎖沒有在規(guī)定的時間內被獲得藻烤,它會讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。
while (1) { [myLock lock]; // 先取出總數(shù) NSInteger count = self.ticketCount; if (count > 0) { self.ticketCount = count - 1; NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount); } else { NSLog(@"票已經(jīng)賣完了"); break; } [myLock unlock]; }
使用@synchronized指令
-
@synchronized指令是在Objective-C代碼中創(chuàng)建一個互斥鎖非常方便的方法怖亭。@synchronized指令做和其他互斥鎖一樣的工作(它防止不同的線程在同一時間獲取同一個鎖)之众。然而在這種情況下,你不需要直接創(chuàng)建一個互斥鎖或鎖對象依许。相反棺禾,你只需要簡單的使用Objective-C對象作為鎖的令牌,如下面例子所示:
- (void)myMethod:(id)anObj { @synchronized(anObj) { // Everything between the braces is protected by the @synchronized directive. } }
創(chuàng)建給@synchronized指令的對象是一個用來區(qū)別保護塊的唯一標示符。如果你在兩個不同的線程里面執(zhí)行上述方法峭跳,每次在一個線程傳遞了一個不同的對象給anObj參數(shù)膘婶,那么每次都將會擁有它的鎖,并持續(xù)處理蛀醉,中間不被其他線程阻塞悬襟。然而,如果你傳遞的是同一個對象拯刁,那么多個線程中的一個線程會首先獲得該鎖脊岳,而其他線程將會被阻塞直到第一個線程完成它的臨界區(qū)
-
作為一種預防措施,@synchronized塊隱式的添加一個異常處理例程來保護代碼垛玻。該處理例程會在異常拋出的時候自動的釋放互斥鎖割捅。這意味著為了使用@synchronized指令,你必須在你的代碼中啟用異常處理帚桩。了如果你不想讓隱式的異常處理例程帶來額外的開銷亿驾,你應該考慮使用鎖的類
while (1) { @synchronized(object) { // 先取出總數(shù) NSInteger count = self.ticketCount; if (count > 0) { self.ticketCount = count - 1; NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount); } else { NSLog(@"票已經(jīng)賣完了"); break; } } }
使用其他Cocoa鎖
NSRecursiveLock
NSRecursiveLock類定義的鎖可以在同一線程多次獲得账嚎,而不會造成死鎖莫瞬。一個遞歸鎖會跟蹤它被多少次成功獲得了。每次成功的獲得該鎖都必須平衡調用鎖住和解鎖的操作郭蕉。只有所有的鎖住和解鎖操作都平衡的時候疼邀,鎖才真正被釋放給其他線程獲得
-
正如它名字所言,這種類型的鎖通常被用在一個遞歸函數(shù)里面來防止遞歸造成阻塞線程召锈。你可以類似的在非遞歸的情況下使用他來調用函數(shù)旁振,這些函數(shù)的語義要求它們使用鎖。以下是一個簡單遞歸函數(shù)烟勋,它在遞歸中獲取鎖规求。如果你不在該代碼里使用NSRecursiveLock對象,當函數(shù)被再次調用的時候線程將會出現(xiàn)死鎖
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; void MyRecursiveFunction(int value) { [theLock lock]; if (value != 0) { --value; MyRecursiveFunction(value); } [theLock unlock]; } MyRecursiveFunction(5);
-
在這種情況下卵惦,如果把代碼進行一下改造
NSLock *theLock = [[NSLock alloc] init];
-
這段代碼是一個典型的死鎖情況阻肿。在我們的線程中,MyRecursiveFunction是遞歸調用的沮尿。所以每次進入這個方法時丛塌,都會去加一次鎖较解,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖赴邻,所以它需要等待鎖被解除印衔,這樣就導致了死鎖,線程被阻塞住了姥敛。調試器中會輸出如下信息:
value = 5 *** -[NSLock lock]: deadlock ( '(null)') *** Break on _NSLockError() to debug. 注意:因為一個遞歸鎖不會被釋放直到所有鎖的調用平衡使用了解鎖操作奸焙,所以你 必須仔細權衡是否決定使用鎖對性能的潛在影響。長時間持有一個鎖將會導致其他 線程阻塞直到遞歸完成彤敛。如果你可以重寫你的代碼來消除遞歸或消除使用一個遞歸 鎖与帆,你可能會獲得更好的性能。
NSCondition
while (1) {
// 先取出總數(shù)
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票墨榄,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
[conditionLock lock];
[conditionLock signal];
[conditionLock unlock];
} else {
NSLog(@"票已經(jīng)賣完了");
break;
}
[conditionLock lock];
[conditionLock wait];
[conditionLock unlock];
}
信號量 dispatch_semaphore
- 信號量機制主要是通過設置有限的資源數(shù)量來控制線程的最大并發(fā)數(shù)量以及阻塞線程實現(xiàn)線程同步等
- GCD中使用信號量需要用到三個函數(shù):
- dispatch_semaphore_create用來創(chuàng)建一個semaphore信號量并設置初始信號量的值玄糟;
- dispatch_semaphore_signal發(fā)送一個信號讓信號量增加1(對應PV操作的V操作);
- dispatch_semaphore_wait等待信號使信號量減1(對應PV操作的P操作)袄秩;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 先取出總數(shù)
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票阵翎,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"票已經(jīng)賣完了");
break;
}
dispatch_semaphore_signal(semaphore);
}