一瑰妄、線程相關(guān)概念
1.1 原子操作
原子和原子操作
原子操作:不可分割的操作石蔗。該操作一定是在同一個CPU時間片
中完成蠢涝,這樣即使線程被切換派阱,在多個線程也不會看到同一個快內(nèi)存中有不完整的數(shù)據(jù)切诀。
原子:不可分割的最小單元揩环。計算機執(zhí)行的最小單元是單條指令
》牵可以通過參考各種CPU的指令操作手冊丰滑,使用匯編指令編寫原子操作,但這種方式非常低效倒庵。
某些簡單的表達式可以被當做現(xiàn)代編程語言的最小執(zhí)行單元褒墨,但其編譯之后得到的匯編指令,不止一條擎宝,所以并不能算真正意義上的原子郁妈。例如常見的加法操作:sum += i
,gcc編譯出來的匯編形式如下:
...
movl 0xc(%ebp), %eax
addl $n, %eax
movl %eax, 0xc(%ebp)
...
而將這段代碼放到多線程環(huán)境下绍申,顯然是不安全的噩咪。再看看下面的例子:
dispatch_group_t group = dispatch_group_create();
__block int i = 1;
for (int k = 0; k < 300; k++) {
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
++i;
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
i;
dispatch_group_leave(group);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"----result=%d i=%d",self.pro1,i);
});
上述例子中,全局變量i
理論上最后得到1极阅,而實際上會有以下結(jié)果:0, -1, 2, -2, 1胃碾。
為了避免錯誤,操作系統(tǒng)或編譯器提供了原子化操作的內(nèi)建函數(shù)或API筋搏。例如:將I++/I
替換為:OSAtomicIncrement32(&i) / OSAtomicDecrement32(&i)
仆百,將得到預期結(jié)果。
atomic
在OC中奔脐,atomic用來修飾@property
屬性儒旬,且默認為atomic栏账,而atomic僅僅是對setter/getter
方法加鎖,只保證了這兩個方法的安全栈源。實際上,線程安全不僅僅只關(guān)注這兩個地方竖般。譬如:對象在一個線程正在執(zhí)行寫操作甚垦,而在另一個線程對象被釋放了,自然就不安全了涣雕。而且在oc中艰亮,我們也可以對成員變量直接賦值,同時操作一塊內(nèi)存挣郭,自然就不安全了迄埃。
1.2 可重入
函數(shù)被重入
一個程序被重入,表示這個函數(shù)沒有執(zhí)行完成兑障,由于外部因素或內(nèi)部調(diào)用侄非,有一次進入函數(shù)執(zhí)行。函數(shù)被重入分兩種情況:
- 多個線程同時執(zhí)行這個函數(shù)
- 函數(shù)自身(可能經(jīng)過多層調(diào)用之后)調(diào)用自身
函數(shù)可重入
一個函數(shù)稱為可重入
的流译,表明該函數(shù)被重入之后沒有產(chǎn)生任何不良后果逞怨。可重入函數(shù)具備以下特點:
- 不使用任何局部/靜態(tài)的非const變量
- 不使用任何局部/靜態(tài)/全局的非const變量的指針
- 僅依賴調(diào)用方法提供的參數(shù)
- 不依賴任何單個資源提供的鎖(互斥鎖等)
不調(diào)用任何不可重入的函數(shù)
可重入是并發(fā)的強力保障一個可重入函數(shù)可以在多線程環(huán)境下放心使用福澡。也就是說在處理多線程問題時叠赦,我們盡量將程序拆分成若干個可重入的函數(shù),而把注意的焦點放在可重入函數(shù)之外的地方革砸。
在函數(shù)式編程
范式中除秀,由于整個系統(tǒng)不需要維護多余數(shù)據(jù)變量,而是狀態(tài)流方式算利。所以可以認為全是由一些可重入的函數(shù)組成册踩,所以函數(shù)式編程在高并發(fā)編程中有其先天優(yōu)勢。
1.3 亂序優(yōu)化與內(nèi)存柵欄
CPU有動態(tài)調(diào)度機制笔时,在執(zhí)行過程中可能因為執(zhí)行效率交換指令的順序棍好。而一些看似獨立的比愛你量實際上是相互影響,這種編譯器優(yōu)化會導致潛在的不確定結(jié)果允耿。
面對這種情況我們一般采用內(nèi)存屏障(memory barrier)
借笙。其作用相當于一個柵欄,迫使處理器來完成位于障礙前面的任何加載和存儲操作之后较锡,才允許它執(zhí)行位于屏障之后的加載和內(nèi)存操作业稼。確保一個線程的內(nèi)存操作總是按照預定的順序完成。為了使用一個內(nèi)存屏障蚂蕴,可以在代碼中需要的地方調(diào)用 OSMemoryBarrier()
函數(shù)低散。
class A {
let lock = NSRecursiveLock()
var _a : A? = nil
var a : A? {
lock.lock()
if _a == nil {
let temp = A()
OSMemoryBarrier()
_a = temp
}
lock.unlock()
return _a
}
}
大部分鎖類型都合并了內(nèi)存屏障俯邓,來確保在進入臨界區(qū)之前,它前面的加載和存儲指令都已經(jīng)完成
1.4 寄存器優(yōu)化與volatile變量
在某些情況下編譯器會把某些變量加載進入寄存器熔号,而如果這些變量對多個線程可見稽鞭,那么這種優(yōu)化可能會阻止其他線程發(fā)現(xiàn)變量的任何變化,從而帶來線程同步問題引镊。
在變量之前加上關(guān)鍵字volatile可以強制編譯器每次使用變量的時候都從內(nèi)存里面加載朦蕴。如果一個變量的值隨時可能給編譯器無法檢測的外部源更改,那么你可以把該變量聲明為volatile變量弟头。在許多原子性操作API中吩抓,大量使用了volatile 標識符修飾。譬如 在系統(tǒng)庫中赴恨,所有原子性變量都使用了
<libkern/OSAtomic.h>
int32_t OSAtomicIncrement32( volatile int32_t *__theValue )
二疹娶、鎖分類
線程同步的主要方式:線程鎖
。線程同步最常用的方法是使用鎖(Lock)
伦连。鎖是一種非強制機制雨饺,每一個線程訪問數(shù)據(jù)或資源之前,首先試圖獲取(Acquireuytreewq)鎖,并在訪問結(jié)束之后釋放(release)除师。在鎖已經(jīng)被占用時獲取鎖沛膳,線程會等待,直到該鎖被釋放汛聚。
2.1 互斥鎖:切換耗性能
基本概念
互斥鎖是在很多平臺上都比較常用的一種鎖锹安。它屬于sleep-waiting
類型的鎖。即當鎖處于占用狀態(tài)時倚舀,其他線程會掛起叹哭,當鎖被釋放時,所有等待的線程都將被喚醒痕貌,再次對鎖進行競爭风罩。在掛起與釋放過程中,涉及用戶態(tài)與內(nèi)核態(tài)之間的context切換舵稠,而這種切換是比較消耗性能的
超升。
pthread_mutex
pthread_mutex 是pthread中的互斥鎖,具有跨平臺性質(zhì)哺徊。pthread是POSIX線程(POSIX threads)的簡稱室琢,是線程的POSIX標準(可移植操作系統(tǒng)接口 Portable Operation System Interface)。POSIX是unix的api設(shè)計標準落追,兼容各大主流平臺盈滴。所以pthread_mutex是比較低層的,可以跨平臺的互斥鎖實現(xiàn)轿钠。
初始化方法:
int pthread_mutex_init(pthread_mutex_t * __restrict, const pthread_mutexattr_t * __restrict);
pthread_mutex_t * __restrict
代表互斥鎖的類型巢钓,有以下四種:
-
PTHREAD_MUTEX_NORMAL 缺省類型
病苗,也就是普通鎖。當一個線程加鎖以后症汹,其余請求鎖的線程將形成一個等待隊列硫朦,并在解鎖后先進先出原則獲得鎖。 -
PTHREAD_MUTEX_ERRORCHECK 檢錯鎖
烈菌,如果同一個線程請求同一個鎖阵幸,則返回 EDEADLK,否則與普通鎖類型動作相同芽世。這樣就保證當不允許多次加鎖時不會出現(xiàn)嵌套情況下的死鎖。 -
PTHREAD_MUTEX_RECURSIVE 遞歸鎖
诡壁,允許同一個線程對同一個鎖成功獲得多次济瓢,并通過多次 unlock 解鎖。線程首次成功獲取互斥鎖時妹卿,鎖定計數(shù)會設(shè)置為 1旺矾。線程每重新鎖定該互斥鎖一次,鎖定計數(shù)就增加 1夺克。線程每解除鎖定該互斥鎖一次箕宙,鎖定計數(shù)就減小 1。 鎖定計數(shù)達到 0 時铺纽,該互斥鎖即可供其他線程獲取柬帕。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤狡门。 -
PTHREAD_MUTEX_DEFAULT 適應鎖
陷寝,動作最簡單的鎖類型,僅等待解鎖后重新競爭其馏,沒有等待隊列。
pthread_mutex_t mutex;
void MyInitFunction()
{
pthread_mutex_init(&mutex, NULL);
}
void MyLockingFunction()
{
pthread_mutex_lock(&mutex);
// Do work.
pthread_mutex_unlock(&mutex);
}
//釋放鎖
pthread_mutex_destroy(&mutex);
pthread_mutex還有一種簡便的調(diào)用方式仔引,使用的是全局唯一互斥鎖
褐奥。實驗表明咖耘,該鎖是所有屬性都是默認的,進程內(nèi)可見鲤看,類型是普通鎖
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
block();
pthread_mutex_unlock(&mutex);
同時它還提供了一種非阻塞版本pthread_mutex_trylock
耍群。若嘗試獲取鎖時發(fā)現(xiàn)互斥鎖已經(jīng)被鎖定义桂,或者超出了遞歸鎖定的最大次數(shù)找筝,則立即返回,不會掛起袖裕。只有在鎖未被占用時才能成功加鎖溉瓶。
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int res = pthread_mutex_trylock(&mutex);
if(res == 0){
block();
pthread_mutex_unlock(&mutex);
}else if(res == EBUSY){
printf("由于 mutex 所指向的互斥鎖已鎖定,因此無法獲取該互斥鎖疾宏。");
}else if (res == EAGAIN){
printf("由于已超出了 mutex 的遞歸鎖定最大次數(shù)触创,因此無法獲取該互斥鎖。");
}
NSLock岩馍、NSRecursiveLock
官方文檔:
Warning
The NSLock class uses POSIX threads to implement its locking behavior. When sending an unlock message to an NSLock object, you must be sure that message is sent from the same thread that sent the initial lock message. Unlocking a lock from a different thread can result in undefined behavior.
You should not use this class to implement a recursive lock. Calling the lock method twice on the same thread will lock up your thread permanently. Use the NSRecursiveLock class to implement recursive locks instead.
Unlocking a lock that is not locked is considered a programmer error and should be fixed in your code. The NSLock class reports such errors by printing an error message to the console when they occur.
-
實現(xiàn)是基于pthread
的蛀恩。 -
誰持有誰釋放
茂浮,試圖釋放由其他線程持有的鎖是不合法的。 -
lock與unlock是一一對應的
佃乘,如果試圖釋放一個沒有加鎖的鎖驹尼,會發(fā)生異常崩潰。而lock始終等不到對應的unlock會進入饑餓狀態(tài)程帕,讓當前線程一直掛起地啰。 - 如果用在需要遞歸嵌套加鎖的場景時亏吝,需要使用其子類NSRecursiveLock。不是所有情況下都會引發(fā)遞歸調(diào)用,而NSLock在性能上要優(yōu)于NSRecursiveLock许赃。而當我們使用NSLock不小心造成死鎖時,可以嘗試將其替換為NSRecursiveLock混聊。
NSLock
使用方式:
BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
//...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
}
NSRecursiveLock
使用方式:
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
[theLock lock];
if (value != 0)
{
--value;
MyRecursiveFunction(value);
}
[theLock unlock];
}
MyRecursiveFunction(5);
@synchronized
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
anObj 是一個唯一標識符句喜,如果在兩個不同線程中執(zhí)行上述方法沟于,并為anObj參數(shù)傳遞了不同的對象,則每個線程都會獲得一個鎖繼續(xù)處理而不會被另一個阻塞拙绊,但如果傳遞相同對象泳秀,則其中一個線程會被阻塞榄攀,直到第一個線程完成檩赢。
@synchronized塊
會在受保護的代碼中隱式添加一個異常處理程序
,如果拋出異常偶房,將自動釋放互斥量军浆。這意味著為了使用該指令乒融,還須在代碼中啟用OC異常處理
。
隱式添加的代碼如下:
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
以上兩個方法的聲明:
/**
* Begin synchronizing on 'obj'.
* Allocates recursive pthread_mutex associated with 'obj' if needed.
* 為傳入的對象分配了一個遞歸鎖愧捕,遞歸鎖在同一線程不會引發(fā)死鎖
* @param obj The object to begin synchronizing on.
*
* @return OBJC_SYNC_SUCCESS once lock is acquired.
*/
OBJC_EXPORT int
objc_sync_enter(id _Nonnull obj)
OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);
/**
* End synchronizing on 'obj'.
*
* @param obj The object to end synchronizing on.
*
* @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
*/
OBJC_EXPORT int
objc_sync_exit(id _Nonnull obj)
OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);
enum {
OBJC_SYNC_SUCCESS = 0,
OBJC_SYNC_NOT_OWNING_THREAD_ERROR = -1
};
兩個方法源碼如下:
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
// 可以看做是鏈表中的一個節(jié)點 關(guān)聯(lián)了object與鎖
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
其中的數(shù)據(jù)結(jié)構(gòu)以及宏定義說明:
//鏈表中的一個節(jié)點次绘,關(guān)聯(lián)object與lock,并且有一個nextdata指向下一個節(jié)點
typedef struct SyncData {
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount; //此時使用這個鎖的線程數(shù)量管跺,因為 SyncData 結(jié)構(gòu)體會被緩存伙菜,如果threadCount==0 說明這個SyncData實例可以被復用了
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
// Use multiple parallel lists to decrease contention among unrelated objects.
#define COUNT 16
#define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1)) //哈希算法將對象所在的內(nèi)存地址轉(zhuǎn)化為無符號整型并右移五位命迈,再跟 0xF 做按位與運算,這樣結(jié)果不會超出數(shù)組大小淑倾。
#define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
#define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
static SyncList sDataLists[COUNT]; //聲明一個SyncList 結(jié)構(gòu)體數(shù)組大小為16
objc_sync_enter
里沒有持有傳入的對象娇哆,假如對象在 synchronized block
中被設(shè)成 nil時 其他線程使用這個對象會一直阻塞嗎勃救?
NSNumber *number = @(1);
NSNumber *thisPtrWillGoToNil = number;
@synchronized (thisPtrWillGoToNil) {
thisPtrWillGoToNil = nil;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
NSCAssert(![NSThread isMainThread], @"Must be run on background thread");
@synchronized (number) {
NSLog(@"This line does indeed get printed to stdout");
}
});
這行代碼還是會打印。OC處理了這種情形勃黍,可能是編譯器做了如下處理:
NSString *test = @"test";
id synchronizeTarget = (id)test;
@try {
objc_sync_enter(synchronizeTarget);
test = nil; //空操作
} @finally {
objc_sync_exit(synchronizeTarget);
}
2.2 自旋鎖:空等耗CPU
自旋鎖 與互斥鎖有點類似覆获,只是自旋鎖被某線程占用時瓢省,其他線程不會進入睡眠(掛起)狀態(tài),而是一直運行(自旋/空轉(zhuǎn))直到鎖被釋放摹量。由于不涉及用戶態(tài)與內(nèi)核態(tài)之間的切換蛔六,它的效率遠遠高于互斥鎖国章。
雖然它的效率比互斥鎖高,但是它也有些不足之處:
- 自旋鎖一直占用CPU骂删,會降低CPU效率。在高并發(fā)執(zhí)行的時候粗恢,或代碼片段比較耗時欧瘪,容易引發(fā)CPU占用率暴漲的風險
- 使用自旋鎖可能造成死鎖,如遞歸調(diào)用時可能會造成死鎖
- 自旋鎖可能引起優(yōu)先級反轉(zhuǎn)的問題妖碉。如果一個低優(yōu)先級的線程獲得鎖并訪問共享資源芥被,這時一個高優(yōu)先級的線程也嘗試獲得這個鎖拴魄,自旋鎖會處于忙等狀態(tài)從而占用大量 CPU。此時低優(yōu)先級線程無法與高優(yōu)先級線程爭奪 CPU 時間夏漱,從而導致任務遲遲完不成顶捷、無法釋放 lock。
在iOS10中建議替換OSSPinLock
為os_unfair_lock
。
// iOS 10以后使用
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
NSLog(@"線程1 準備上鎖");
os_unfair_lock_lock(unfairLock);
sleep(4);
NSLog(@"線程1");
os_unfair_lock_unlock(unfairLock);
NSLog(@"線程1 解鎖成功");
NSLog(@"---------------------------------------");
解決優(yōu)先級反轉(zhuǎn)有兩種方法:調(diào)整優(yōu)先級
-
優(yōu)先級天花板
是當任務申請鎖時专肪,把該任務優(yōu)先級提升到可訪問這個資源的所有任務中的最高優(yōu)先級堪侯。 -
優(yōu)先級繼承
是當任務A申請共享資源S時伍宦,如果S正在被任務C使用,通過比較任務C與自身的優(yōu)先級关贵,如發(fā)現(xiàn)任務C優(yōu)先級小于自身優(yōu)先級卖毁,則將任務C的優(yōu)先級提升到自身優(yōu)先級。C釋放資源后炭剪,在恢復C的優(yōu)先級奴拦。
2.3 信號量
dispatch_semaphore是GCD用于控制多線程并發(fā)的信號量,通過wait/signal的信號事件控制并發(fā)執(zhí)行的最大線程數(shù)绿鸣,信號量不支持遞歸站玄。
當信號量為0時,dispatch_wait 會阻塞線程再登,可以利用這點特性實現(xiàn)控制代碼塊最大并發(fā)數(shù)锉矢,或?qū)惒骄€程轉(zhuǎn)為同步齿尽。
dispatch_semaphore_signal
源碼:
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
//對信號量執(zhí)行+1操作
long value = os_atomic_inc2o(dsema, dsema_value, release);
// 如果值大于0 直接返回
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
DISPATCH_NOINLINE
long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
_dispatch_sema4_signal(&dsema->dsema_sema, 1);
return 1;
}
dispatch_semaphore_wait
源碼:
DISPATCH_NOINLINE
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout)
{
long orig;
_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
switch (timeout) {
default:
if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
orig = dsema->dsema_value;
while (orig < 0) {
if (os_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,&orig, relaxed)) {
return _DSEMA4_TIMEOUT();
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
_dispatch_sema4_wait(&dsema->dsema_sema);
break;
}
return 0;
}
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// 信號量-1
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
// 如果值大于等于0 直接返回
if (likely(value >= 0)) {
return 0;
}
// 否則開始阻塞當前線程
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
2.4 條件鎖
NSCondition
- lock/unlock:加鎖/解鎖绵估;
- wait:在鎖中間等待卡骂;
- signal:喚醒一個等待的線程,如果有多個缝左,只能喚醒第一個浓若;
- broadcast:喚醒所有等待的線程挪钓;
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程1加鎖成功");
[cLock wait];
NSLog(@"線程1");
[cLock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程2加鎖成功");
[cLock wait];
NSLog(@"線程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"喚醒一個等待的線程");
[cLock signal];
//[cLock broadcast] 喚醒所有等待的線程
});
輸出
線程1加鎖成功
線程2加鎖成功
喚醒一個等待的線程
線程1
NSConditionLock
條件鎖碌上,可以用于實現(xiàn)任務間的依賴
- initWithCondition:設(shè)置condition初始值挽放;
- tryLockWhenCondition:滿足- condition值時上鎖蔓纠,并返回上鎖成功與否腿倚;
- lockWhenCondition:滿足- condition值時上鎖;
- unlockWithCondition:解鎖暂筝,并設(shè)置condition為參數(shù)值硬贯;
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"線程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失敗");
}
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"線程2");
[cLock unlockWithCondition:2];
});
//線程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"線程3");
[cLock unlockWithCondition:3];
});
輸出:
線程1
線程3
線程2
2.5 讀寫鎖
讀寫鎖 從廣義的邏輯上講饭豹,也可以認為是一種共享版的互斥鎖。如果對一個臨界區(qū)大部分是讀操作而只有少量的寫操作它褪,讀寫鎖在一定程度上能夠降低線程互斥產(chǎn)生的代價翘悉。
對于同一個鎖妖混,讀寫鎖有兩種獲取鎖的方式:共享(share)方式,獨占(Exclusive)方式诗越。寫操作獨占息堂,讀操作共享
讀寫鎖狀態(tài) | 以共享方式獲取(讀操作) | 以獨占方式獲取(寫操作) |
---|---|---|
自由 | 成功 | 成功 |
共享 | 成功 | 等待 |
獨占 | 等待 | 等待 |
//讀
pthread_rwlock_rdlock(&rwLock);
pthread_rwlock_unlock(&rwLock);
//寫
pthread_rwlock_wrlock(&rwLock);
pthread_rwlock_unlock(&rwLock);