iOS多執行緒程式設計:執行緒同步總結

子非あ魚發表於2013-09-25
1:原子操作 - OSAtomic系列函式

iOS平臺下的原子操作函式都以OSAtomic開頭,使用時需要包含標頭檔案<libkern/OSBase.h>。不同執行緒如果通過原子操作函式對同一變數進行操作,可以保證一個執行緒的操作不會影響到其他執行緒內對此變數的操作,因為這些操作都是原子式的。因為原子操作只能對內建型別進行操作,所以原子操作能夠同步的執行緒只能位於同一個程式的地址空間內。

2:鎖 - NSLock系列物件

iOS平臺下的鎖物件為NSLock物件,進入鎖通過呼叫lock函式,解鎖呼叫unlock函式(因為iOS中大部分的執行緒同步類都繼承自NSLocking協議,所以其加鎖/解鎖的操作基本都為lock/unlock函式),同一個NSLock物件成功呼叫lock函式後,在其顯式unlock之前任何執行緒都不能再對此NSLock物件加鎖,以達到互斥訪問的目的。除了lock函式,對NSLock加鎖的函式還包括tryLock以及lockBeforeDate函式,lock函式在成功加鎖之間會一直阻塞,而tryLock會嘗試加鎖,如果不成功,不會阻塞,而是直接返回NO,lockBeforeDate則是阻塞到傳入的NSDate日期為止。

除了NSLock,iOS還提供了NSRecursive、NSConditionLock型別的鎖型別。NSRecursive與NSLock最大的區別就是NSRecursive是可重入的,也就是說一個執行緒可以對一個NSRecursive物件多次呼叫lock,只要解鎖時呼叫相同次數的unlock函式便可。NSConditionLock是一種帶有條件的鎖物件,除了基本的lock與unlock函式,還提供了lockWithCondition以及unlockWithCondition,這兩個函式接收整型型別的資料作為引數,只有當一個unlockWithCondition物件被呼叫時,對應的lockWithCondition才會正常返回。這種機制在需幾多個執行緒順序化的完成某個任務時比較有用,例程如下:

 

[plain] view plaincopy
 
//執行緒A  
id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];  
   
while(true)  
{  
    [condLock lock];  
    /* Add data to the queue. */  
    [condLock unlockWithCondition:HAS_DATA];  
}  

  

 [plain] view plaincopy

 
//執行緒B  
while (true)  
{  
    [condLock lockWhenCondition:HAS_DATA];  
    /* Remove data from the queue. */  
    [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];  
   
    // Process the data locally.  
}   

除了顯示的生成NSLock系列物件,還可以通過將程式碼放到@synchronized內來達到同步的目的,一段放入其內的程式碼,不同的執行緒是不能重入的例如:

 

[plain] view plaincopy
 
- (void)myMethod:(id)anObj  
{  
    @synchronized(anObj)  
    {  
        //此處程式碼在同一時刻只能有一個執行緒執行.  
    }  
}  

  


NSLock系列物件都是可以具名的,也就是說,這些物件可以用於不同程式內部的執行緒的同步。

3:事件 - NSCondtion

NSConditon型別提供了wait與signal函式,分別代表了等待事件的操作以及觸發事件的操作。除了wait函式,NSCondition還提供了waitUntilDate函式,其功能與NSLock中的lockBeforeDate大致相同,簡要來說就是提供了一個帶超時的wait函式。

雖然NSCondition與Windows環境下Event型別所完成的功能大致類似,但對一個熟悉Event型別的開發人員來說,NSConditon的行為會有點奇怪:

第一點:因為遵循NSLocking協議,所以NSCondition在觸發與等待過程的前後要分別呼叫lock與unlock函式,前面提到過,當一個遵循NSLocking協議的物件呼叫lock後,其他的對此物件的lock呼叫都會阻塞。那麼,如果兩個執行緒A和B,A要觸發事件,B接收事件,B執行緒在呼叫lock後,通過呼叫wait函式進入等待事件觸發的狀態,那麼,A執行緒豈不是再也沒有機會對這個事件進行觸發了(因為此物件已經被B執行緒lock)?祕密就在於wait函式的呼叫,其實,在wait函式內部悄悄的呼叫了unlock函式,也就是說在呼叫wati函式後,這個NSCondition物件就處於了無鎖的狀態,這樣A執行緒就可以對此物件加鎖並觸發該NSCondition物件。當一個事件被其他執行緒觸發時,在wait函式內部得到此事件被觸發的通知,然後對此事件重新呼叫lock函式,然後函式返回,而在函式外部,看起來好像接收事件的執行緒從來沒有放開NSCondition物件的所有權,B執行緒直接由阻塞狀態進入了觸發狀態。

第二點:當有多個執行緒進入阻塞狀態,等待同一個AutoReset的Event物件被觸發時,在Windows環境下喚醒哪一個執行緒是沒有固定的順序的,也就是說作業系統對喚醒哪一個執行緒不會提供任何的保證。而在iOS平臺上,經過筆者測試,其被觸發的順序與,並且只與呼叫wait函式的順序相關,與其他(比如執行緒優先順序)條件沒有關係。這一點在開發時需要進行額外的考慮。

第三點:wait函式並不是完全可信的。這一點比較讓人蛋疼,也就是說wait返回後,並不代表對應的事件一定被觸發了,因此,為了保證執行緒之間的同步關係,使用NSCondtion時往往需要加入一個額外的變數來對非正常的wait返回進行規避。具體示例程式碼如下:

 

[plain] view plaincopy
 
//等待事件觸發的執行緒  
[cocoaCondition lock];  
while (timeToDoWork <= 0)  
    [cocoaCondition wait];  
   
timeToDoWork--;  
   
// Do real work here.  
   
[cocoaCondition unlock];  
  
//出發事件的執行緒  
[cocoaCondition lock];  
timeToDoWork++;  
[cocoaCondition signal];  
[cocoaCondition unlock];  

 

個timeToDoWork就是那個額外需要的變數,在NSCondition的使用中,這個變數是必不可少的。

 

NSConditon物件也是具名的,也就是說,其可於不同程式內部的執行緒同步。

相關文章