鎖是線程編程同步工具的基礎。鎖可以讓你很容易保護代碼中一大塊地區以便你可以確保代碼的正確性。 1 使用POSIX互斥鎖 POSIX互斥鎖在很多程式裡面很容易使用。為了建立一個互斥鎖,你聲明並初始化一個pthread_mutex_t的結構。為了鎖住和解鎖一個互斥鎖,你可以使用pthread_mutex_lock和pthread_mutex_unlock函數。列表4-2顯式了要初始化並使用一個POSIX線程的互斥鎖的基礎代碼。當你用完一個鎖之後,只要簡單的調用pthread_mutex_destroy來釋放該鎖的資料結構。 Listing 4-2 Using a mutex lock pthread_mutex_t mutex; void MyInitFunction() { pthread_mutex_init(&mutex, NULL); } void MyLockingFunction() { pthread_mutex_lock(&mutex); // Do work. pthread_mutex_unlock(&mutex); } 注意:上面的代碼只是簡單的顯式了使用一個POSIX線程互斥鎖的步驟。你自己的代碼應該檢查這些函數返回的錯誤碼,並適當的處理它們。4.6.2 使用NSLock類 在Cocoa程式中NSLock中實現了一個簡單的互斥鎖。所有鎖(包括NSLock)的介面實際上都是通過NSLocking協議定義的,它定義了lock和unlock方法。你使用這些方法來擷取和釋放該鎖。 除了標準的鎖行為,NSLock類還增加了tryLock和lockBeforeDate:方法。方法tryLock試圖擷取一個鎖,但是如果鎖停用時候,它不會阻塞線程。相反,它只是返回NO。而lockBeforeDate:方法試圖擷取一個鎖,但是如果鎖沒有在規定的時間內被獲得,它會讓線程從阻塞狀態變為非阻塞狀態(或者返回NO)。 下面的例子顯式了你可以是NSLock對象來協助更新一個可視化顯式,它的資料結構被多個線程計算。如果線程沒有立即獲的鎖,它只是簡單的繼續計算直到它可以獲得鎖再更新顯式。 BOOL moreToDo = YES; NSLock *theLock = [[NSLock alloc] init]; ... while (moreToDo) { if ([theLock tryLock]) { [theLock
unlock]; } }4.6.3 使用@synchronized指令 @synchronized指令是在Objective-C代碼中建立一個互斥鎖非常方便的方法。@synchronized指令做和其他互斥鎖一樣的工作(它防止不同的線程在同一時間擷取同一個鎖)。然而在這種情況下,你不需要直接建立一個互斥鎖或鎖對象。相反,你只需要簡單的使用Objective-C對象作為鎖的令牌,如下面例子所示: - (void)myMethod:(id)anObj { @synchronized(anObj) { //
Everything between the braces is protected by the @synchronized directive. } } 建立給@synchronized指令的對象是一個用來區別保護塊的唯一標示符。如果你在兩個不同的線程裡面執行上述方法,每次在一個線程傳遞了一個不同的對象給anObj參數,那麼每次都將會擁有它的鎖,並持續處理,中間不被其他線程阻塞。然而,如果你傳遞的是同一個對象,那麼多個線程中的一個線程會首先獲得該鎖,而其他線程將會被阻塞直到第一個線程完成它的臨界區。 作為一種預防措施,@synchronized塊隱式的添加一個異常處理常式來保護代碼。該處理常式會在異常拋出的時候自動的釋放互斥鎖。這意味著為了使用@synchronized指令,你必須在你的代碼中啟用異常處理。了如果你不想讓隱式的異常處理常式帶來額外的開銷,你應該考慮使用鎖的類。4.6.4 使用其他Cocoa鎖 以下個部分描述了使用Cocoa其他類型的鎖。·使用NSRecursiveLock對象 NSRecursiveLock類定義的鎖可以在同一線程多次獲得,而不會造成死結。一個遞迴鎖會跟蹤它被多少次成功獲得了。每次成功的獲得該鎖都必須平衡調用鎖住和解鎖的操作。只有所有的鎖住和解鎖操作都平衡的時候,鎖才真正被釋放給其他線程獲得。 正如它名字所言,這種類型的鎖通常被用在一個遞迴函式裡面來防止遞迴造成阻塞線程。你可以類似的在非遞迴的情況下使用他來調用函數,這些函數的語義要求它們使用鎖。以下是一個簡單遞迴函式,它在遞迴中擷取鎖。如果你不在該代碼裡使用NSRecursiveLock對象,當函數被再次調用的時候線程將會出現死結。
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; void MyRecursiveFunction(int value) { [theLock lock]; if (value != 0) { --value; MyRecursiveFunction(value); } [theLock unlock]; } MyRecursiveFunction(5); 注意:因為一個遞迴鎖不會被釋放直到所有鎖的調用平衡使用瞭解鎖操作,所以你必須仔細權衡是否決定使用鎖對效能的潛在影響。長時間持有一個鎖將會導致其他線程阻塞直到遞迴完成。如果你可以重寫你的代碼來消除遞迴或消除使用一個遞迴鎖,你可能會獲得更好的效能。·使用NSConditionLock對象 NSConditionLock對象定義了一個互斥鎖,可以使用特定值來鎖住和解鎖。不要把該類型的鎖和條件(參見“條件”部分)混淆了。它的行為和條件有點類似,但是它們的實現非常不同。 通常,當多線程需要以特定的順序來執行任務的時候,你可以使用一個NSConditionLock對象,比如當一個線程生產資料,而另外一個線程消費資料。生產者執行時,消費者使用由你程式指定的條件來擷取鎖(條件本身是一個你定義的整形值)。當生產者完成時,它會解鎖該鎖並設定鎖的條件為合適的整形值來喚醒消費者線程,之後消費線程繼續處理資料。 NSConditionLock的鎖住和解鎖方法可以任意組合使用。比如,你可以使用unlockWithCondition:和lock訊息,或使用lockWhenCondition:和unlock訊息。當然,後面的組合可以解鎖一個鎖但是可能沒有釋放任何等待某特定條件值的線程。 下面的例子顯示了生產者-消費者問題如何使用條件鎖來處理。想象一個應用程式套件組合含一個資料的隊列。一個生產者線程把資料添加到隊列,而消費者線程從隊列中取出資料。生產者不需要等待特定的條件,但是它必須等待鎖可用以便它可以安全的把資料添加到隊列。 id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; while(true) { [condLock lock]; [condLock unlockWithCondition:HAS_DATA]; } 因為初始化條件鎖的值為NO_DATA,生產者線程在初始化的時候可以毫無問題的擷取該鎖。它會添加隊列資料,並把條件設定為HAS_DATA。在隨後的迭代中,生產者線程可以把到達的資料添加到隊列,無論隊列是否為空白或依然有資料。唯一讓它進入阻塞的情況是當一個消費者線程充隊列取出資料的時候。 因為消費者線程必須要有資料來處理,它會使用一個特定的條件來等待隊列。當生產者把資料放入隊列時,消費者線程被喚醒並擷取它的鎖。它可以從隊列中取出資料,並更新隊列的狀態。下列代碼顯示了消費者線程處理迴圈的基本結構。 while (true) { [condLock lockWhenCondition:HAS_DATA]; [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)]; // Process the data locally. }·使用NSDistributedLock對象 NSDistributedLock類可以被多台主機上的多個應用程式使用來限制對某些共用資源的訪問,比如一個檔案。鎖本身是一個高效的互斥鎖,它使用檔案系統項目來實現,比如一個檔案或目錄。對於一個可用的NSDistributedLock對象,鎖必須由所有使用它的程式寫入。這通常意味著把它放在檔案系統,該檔案系統可以被所有運行在電腦上面的應用程式訪問。 不像其他類型的鎖,NSDistributedLock並沒有實現NSLocking協議,所有它沒有lock方法。一個lock方法將會阻塞線程的執行,並要求系統以預定的速度輪詢鎖。以其在你的代碼中實現這種約束,NSDistributedLock提供了一個tryLock方法,並讓你決定是否輪詢。 因為它使用檔案系統來實現,一個NSDistributedLock對象不會被釋放除非它的擁有者顯式的釋放它。如果你的程式在使用者一個分布鎖的時候崩潰了,其他用戶端無法訪問該受保護的資源。在這種情況下,你可以使用breadLock方法來打破現存的鎖以便你可以擷取它。但是通常應該避免打破鎖,除非你確定擁有進程已經死亡並不可能再釋放該鎖。
和其他類型的鎖一樣,當你使用NSDistributedLock對象時,你可以通過調用unlock方法來釋放它。