本文主要是一個(gè)對(duì)各種鎖的簡(jiǎn)單整理彬犯,方便后續(xù)查看回顧,畢竟鎖在實(shí)際項(xiàng)目中用的比較少田炭,再牛逼的知識(shí)點(diǎn)师抄,老不用也記不住。好記性不如爛筆頭教硫。
說(shuō)到鎖肯定是涉及多線程了叨吮,所以先簡(jiǎn)單的介紹一下iOS里的多線程GCD。
先介紹幾個(gè)比較容易混淆的術(shù)語(yǔ):
- 同步:在當(dāng)前線程中執(zhí)行任務(wù)瞬矩,不具備開(kāi)啟新線程的能力茶鉴;
- 異步:在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力景用;
- 并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行蛤铜;
- 串行:一個(gè)任務(wù)執(zhí)行完成之后,再執(zhí)行下一個(gè)任務(wù)丛肢。
各種隊(duì)列的執(zhí)行效果
并發(fā)隊(duì)列 | 手動(dòng)創(chuàng)建的串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步(sync) | 沒(méi)有開(kāi)啟新的線程围肥;串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新的線程;串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新的線程蜂怎;串行執(zhí)行任務(wù) |
異步(async) | 有開(kāi)啟新的線程穆刻;并發(fā)執(zhí)行任務(wù) | 有開(kāi)啟新的線程;串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新的線程杠步;串行執(zhí)行任務(wù) |
注意: 使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù)氢伟,會(huì)卡在當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)
隊(duì)列組的使用
使用GCD實(shí)現(xiàn):異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2幽歼。等任務(wù)1和2執(zhí)行完之后朵锣,回到主線程執(zhí)行任務(wù)3
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行任務(wù)一");
});
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行任務(wù)二");
});
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程執(zhí)行任務(wù)三");
});
});
多線程的安全隱患:
- 當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題甸私。
iOS中的線程同步方案
GNUstep
- 說(shuō)明:GNUstep是GNU計(jì)劃的項(xiàng)目之一诚些,它將Cocoa的OC庫(kù)重新開(kāi)源實(shí)現(xiàn)的一遍
- 源碼地址:http://www.gnustep.org/resources/downloads.php
- 雖然GNUstep不是蘋果官方源碼,但還是有一定的參考價(jià)值
1. OSSpinLock
- 介紹:OSSpinLock叫做“自旋鎖”,等待所得線程會(huì)處于忙等(busy-wait)狀態(tài)诬烹,一直占用著CPU資源砸烦;
- 目前已不再安全,可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題绞吁;
- 如果等待鎖的線程優(yōu)先級(jí)較高幢痘,它會(huì)一直占用著CPU資源,優(yōu)先級(jí)低的線程就無(wú)法釋放鎖家破;
- 需要導(dǎo)入頭文件 #import <libkern/OSAtomic.h>
- 使用方法:
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 嘗試加鎖(如果需要等待就不加鎖颜说,直接返回false;如果不需要等待汰聋,就加鎖门粪,返回true)
bool result = OSSpinLockTry(&lock);
// 加鎖
OSSpinLockLock(&lock);
// 解鎖
OSSpinLockUnlock(&lock);
2. os_unfair_lock
- 介紹:os_unfair_lock用于取代不安全的OSSpinLock,從iOS10才開(kāi)始支持马僻;
- 從底層調(diào)用看庄拇,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等韭邓;
- 需要導(dǎo)入頭文件 #import <os/lock.h>
- 使用方法:
// 初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// 嘗試加鎖(如果需要等待就不加鎖措近,直接返回false;如果不需要等待女淑,就加鎖瞭郑,返回true)
bool result = os_unfair_lock_trylock(&lock);
// 加鎖
os_unfair_lock_lock(&lock);
// 解鎖
os_unfair_lock_unlock(&lock);
3. pthread_mutex
- 介紹:mutex叫做“互斥鎖”,等待鎖的線程會(huì)處于休眠狀態(tài)鸭你∏牛跨平臺(tái)
- 需要導(dǎo)入頭文件 #import <pthread.h>
- 使用方法:
// 初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
// 注意 如果屬性值傳NULL,則屬性的默認(rèn)值為上面的代碼
<!--pthread_mutex_init(&mutex, NULL);-->
// 嘗試加鎖
pthread_mutex_trylock(&mutex);
// 加鎖
pthread_mutex_lock(&mutex);
// 解鎖
pthread_mutex_unlock(&mutex);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
// 銷毀鎖
pthread_mutex_destroy(&mutex);
pthread_mutex遞歸鎖
- 創(chuàng)建時(shí)在初始化屬性的時(shí)候袱巨,傳參為:PTHREAD_MUTEX_RECURSIVE
- 即:
// 初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
pthread_mutex條件鎖
- 用法:
// 初始化鎖 NULL代表使用默認(rèn)屬性
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// 初始化條件
pthread_cond_t condition;
pthread_cond_init(&condition, NULL);
// 等待條件(進(jìn)入休眠阁谆,放開(kāi)mutex鎖,被喚醒后愉老,會(huì)再次對(duì)mutex加鎖)
pthread_cond_wait(&condition, &mutex);
// 激活一個(gè)等待該條件的線程
pthread_cond_signal(&condition);
// 激活所有等待該條件的線程
pthread_cond_broadcast(&condition);
// 銷毀資源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condition);
4. dispatch_semaphore
- 介紹:信號(hào)量鎖
- 信號(hào)量的初始值场绿,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量;
- 信號(hào)量的初始值為1嫉入,代表同時(shí)只允許1條線程訪問(wèn)資源焰盗,保證線程同步;
- 簡(jiǎn)單用法:
// 初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 如果信號(hào)量的值 <= 0咒林,當(dāng)前線程就會(huì)進(jìn)入休眠等待(直到信號(hào)量的值 > 0)
// 如果信號(hào)量的值 > 0熬拒,就減1,然后往下執(zhí)行后面的代碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 讓信號(hào)量的值加1
dispatch_semaphore_signal(semaphore);
5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
- 使用GCD的串行隊(duì)列垫竞,也是可以實(shí)現(xiàn)線程同步的
dispatch_queue_t queue = dispatch_queue_create("lock_queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 任務(wù)
});
6. NSLock
- 介紹:NSLock是對(duì)mutex普通鎖的封裝澎粟;
- 方法API:
- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
7. NSRecursiveLock
- 介紹:NSRecursiveLock是對(duì)mutex遞歸鎖的封裝;
- 用法和NSLock基本類似;
8. NSCondition
- 介紹:NSCondition是對(duì)mutex條件鎖的封裝捌议;
- 方法API:
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
9. NSConditionLock
- 介紹:NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝哼拔,可以設(shè)置具體的條件值引有;
- 主要API:
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
10. @synchronized
- 介紹:@synchronized是對(duì)mutex的封裝瓣颅;
- 源碼查看:objc4中的objc-sync.mm文件;
- @synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖譬正,然后進(jìn)行加鎖宫补、解鎖操作;
- 用法事例:
@synchronized(obj) {
// 任務(wù)
}
iOS線程同步方案性能大比拼 (從高到低)
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
自旋鎖與互斥鎖的比較
什么時(shí)候用自旋鎖比較劃算曾我?
- 預(yù)計(jì)線程等待鎖的時(shí)間很短粉怕;
- 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生抒巢;
- CPU資源不緊張
- 多核處理器
什么情況用互斥鎖比較劃算贫贝?
- 預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng);
- 單核處理器蛉谜;
- 臨界區(qū)有IO操作稚晚;
- 臨界區(qū)代碼復(fù)雜或者循環(huán)量較大
- 臨界區(qū)競(jìng)爭(zhēng)非常激烈;
注意:蘋果其實(shí)不推薦使用自旋鎖
atomic
- 介紹:atomic用戶保證屬性setter型诚、getter的原子性操作客燕,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖;
- 可以參考o(jì)bjc4的objc-accessors.mm文件
- 它并不能保證使用屬性的過(guò)程是線程安全的狰贯;
iOS中的讀寫安全方案
應(yīng)用場(chǎng)景:
- 同一時(shí)間也搓,只能有1個(gè)線程進(jìn)行寫的操作;
- 同一時(shí)間涵紊,允許有多個(gè)線程進(jìn)行讀的操作傍妒;
- 同一時(shí)間,不允許既有寫的操作摸柄,又有讀的操作颤练;
對(duì)于上面的“多讀單寫”,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作塘幅,iOS中有兩種方案:
- pthread_rwlock:讀寫鎖昔案;
- dispatch_barrier_async:異步柵欄調(diào)用;
pthread_rwlock 用法:
// 初始化鎖
pthread_rwlock_t lock;
// 初始化鎖
pthread_rwlock_init(&lock, NULL);
// 讀-加鎖
pthread_rwlock_rdlock(&lock);
// 讀-嘗試加鎖
pthread_rwlock_tryrdlock(&lock);
// 寫-加鎖
pthread_rwlock_wrlock(&lock);
// 寫-嘗試加鎖
pthread_rwlock_trywrlock(&lock);
// 解鎖
pthread_rwlock_unlock(&lock);
// 銷毀
pthread_rwlock_destroy(&lock);
dispatch_barrier_async
- 這個(gè)函數(shù)傳入的并發(fā)隊(duì)列必須是自己通過(guò)dispatch_queue_create創(chuàng)建的
- 如果傳入的是一個(gè)串行或者一個(gè)全局的并發(fā)隊(duì)列电媳,那么這個(gè)函數(shù)便等同于dispatch_async函數(shù)的效果踏揣;
- 簡(jiǎn)單用法:
// 初始化隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
// 讀
dispatch_async(queue, ^{
});
// 寫
dispatch_barrier_async(queue, ^{
});