iOS中的常見多線程方案

GCD的常用函數(shù)
GCD中有2個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù)
用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
GCD的隊(duì)列
GCD的隊(duì)列可以分為2大類型
==并發(fā)==隊(duì)列(Concurrent Dispatch Queue)
可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效==串行==隊(duì)列(Serial Dispatch Queue)
讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
容易混淆的術(shù)語(yǔ)
有4個(gè)術(shù)語(yǔ)比較容易混淆:==同步、異步、并發(fā)、串行==
同步和異步主要影響:能不能開啟新的線程
- 同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
- 異步:在新的線程中(主線程除外)執(zhí)行任務(wù),具備開啟新線程的能力
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
- 并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
- 串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
各種隊(duì)列的執(zhí)行效果

==使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)==
// 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
// dispatch_sync立馬在當(dāng)前線程同步執(zhí)行任務(wù)
// 問(wèn)題:以下代碼是在主線程執(zhí)行的,會(huì)不會(huì)產(chǎn)生死鎖?會(huì)!
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 0
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue, ^{ // 1
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)5");
隊(duì)列組的使用
思考:如何用gcd實(shí)現(xiàn)以下功能
異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3
// 創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
// 創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加異步任務(wù)
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)2-%@", [NSThread currentThread]);
}
});
// 等前面的任務(wù)執(zhí)行完畢后,會(huì)自動(dòng)執(zhí)行這個(gè)任務(wù)
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務(wù)3-%@", [NSThread currentThread]);
}
});
多線程的安全隱患
資源共享
1塊資源可能會(huì)被多個(gè)線程共享,也就是==多個(gè)線程可能會(huì)訪問(wèn)同一塊資源==
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象、同一個(gè)變量、同一個(gè)文件,當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)==數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全==問(wèn)題
多線程安全隱患示例 – 存錢取錢

多線程安全隱患示例 – 賣票


多線程安全隱患的解決方案
- 解決方案:使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
- 常見的線程同步技術(shù)是:加鎖
GNUstep
GNUstep是GNU計(jì)劃的項(xiàng)目之一,它將Cocoa的OC庫(kù)重新開源實(shí)現(xiàn)了一遍
雖然GNUstep不是蘋果官方源碼,但還是具有一定的參考價(jià)值
iOS中的線程同步方案
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
OSSpinLock
OSSpinLock叫做”自旋鎖”,等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài),一直占用著CPU資源
目前已經(jīng)不再安全,可能會(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)
OSSpinLockTry(&lock)
// 加鎖
OSSpinLockLock(&lock);
// 解鎖
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,從iOS10開始才支持
從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等
需要導(dǎo)入頭文件#import <os/lock.h>
-** Low-level lock的特點(diǎn)等不到鎖就休眠
//初始化
os_unfair_lock moneyLock = OS_UNFAIR_LOCK_INIT;
//嘗試加鎖
os_unfair_lock_trylock(&moneyLock);
//加鎖
os_unfair_lock_lock(&moneyLock);
//具體操作
//解鎖
os_unfair_lock_unlock(&moneyLock);
pthread_mutex
mutex叫做==”互斥鎖”==,等待鎖的線程會(huì)處于休眠狀態(tài)
需要導(dǎo)入頭文件#import <pthread.h>
pthread_mutex_t moneyMutex;
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化鎖
pthread_mutex_init(mutex, &attr);
// 銷毀屬性
pthread_mutexattr_destroy(&attr);
//嘗試加鎖
pthread_mutex_trylock(&moneyMutex);
//加鎖
pthread_mutex_lock(&moneyMutex);
//具體操作
//解鎖
pthread_mutex_unlock(&moneyMutex);
//銷毀鎖
pthread_mutex_destroy(&moneyMutex);
Mutex type attributes
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
- pthread_mutex – 遞歸鎖
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- pthread_mutex – 條件
pthread_cond_t cond;
// 初始化條件
pthread_cond_init(&_cond, NULL);
if (條件) {
// 等待條件(進(jìn)入休眠,放開mutex鎖;唄喚醒后,會(huì)再次對(duì)mutex加鎖)
pthread_cond_wait(&_cond, &_mutex);
}
// 信號(hào)(激活一個(gè)等待條件的線程)
pthread_cond_signal(&_cond);
// 廣播(激活所有等待條件的線程)
//pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
NSLock、NSRecursiveLock
NSLock是對(duì)mutex普通鎖的封裝
NSRecursiveLock也是對(duì)mutex遞歸鎖的封裝,API跟NSLock基本一致
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSCondition
==NSCondition==是對(duì)==mutex==和==cond==的封裝
@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSConditionLock
NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝,可以設(shè)置具體的條件值
@interface NSConditionLock : NSObject <NSLocking> {
- (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;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
dispatch_semaphore
==semaphore==叫做”信號(hào)量”
信號(hào)量的初始值,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問(wèn)資源,保證線程同步
//初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 如果信號(hào)量的值 > 0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
// 如果信號(hào)量的值 <= 0,就會(huì)休眠等待,直到信號(hào)量的值變成>0,就讓信號(hào)量的值減1,然后繼續(xù)往下執(zhí)行代碼
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 讓信號(hào)量的值+1
dispatch_semaphore_signal(self.semaphore);
Semaphore宏定義
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
使用方式
SemaphoreBegin;
// 操作
SemaphoreEnd;
dispatch_queue
直接使用GCD的串行隊(duì)列,也是可以實(shí)現(xiàn)線程同步的
dispatch_queue_t moneyQueue;
self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(moneyQueue, ^{
//任務(wù)
});
@synchronized
==@synchronized==是對(duì)==mutex==遞歸鎖的封裝
源碼查看:objc4中的objc-sync.mm文件
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});
@synchronized(lock) {
[super __saleTicket];
}
iOS線程同步方案性能比較
性能從高到低排序
- os_unfair_lock(iOS10以后)
- OSSpinLock(廢棄,存在問(wèn)題)
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
自旋鎖、互斥鎖比較
- 什么情況使用自旋鎖比較劃算?
- 預(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)非常激烈
atomic
atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
可以參考源碼objc4的objc-accessors.mm
它并不能保證使用屬性的過(guò)程是線程安全的
iOS中的讀寫安全方案
- 思考如何實(shí)現(xiàn)以下場(chǎng)景
- 同一時(shí)間,只能有1個(gè)線程進(jìn)行寫的操作
- 同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作
- 同一時(shí)間,不允許既有寫的操作,又有讀的操作
- 上面的場(chǎng)景就是典型的“多讀單寫”,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,iOS中的實(shí)現(xiàn)方案有
- pthread_rwlock:讀寫鎖
- dispatch_barrier_async:異步柵欄調(diào)用
pthread_rwlock
等待鎖的線程會(huì)進(jìn)入休眠
@property (assign, nonatomic) pthread_rwlock_t lock;
- (void)rwlockTest {
// 初始化鎖
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
pthread_rwlock_rdlock(&_lock);
//讀任務(wù)
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
pthread_rwlock_wrlock(&_lock);
//寫任務(wù)
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
pthread_rwlock_destroy(&_lock);
}
dispatch_barrier_async
- 這個(gè)函數(shù)傳入的并發(fā)隊(duì)列必須是自己通過(guò)dispatch_queue_cretate創(chuàng)建的
- 如果傳入的是一個(gè)串行或是一個(gè)全局的并發(fā)隊(duì)列,那這個(gè)函數(shù)便等同于dispatch_async函數(shù)的效果
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];//讀操作
});
dispatch_barrier_async(self.queue, ^{
[self write];//寫操作
});
}
- 隊(duì)列的特點(diǎn):排隊(duì)、FIFO
dispatch_sync:立刻在當(dāng)前線程執(zhí)行任務(wù),執(zhí)行完畢才能繼續(xù)往下執(zhí)行
si指令
s->單步 step一行oc代碼
i->instruction一行匯編代碼
next -> 區(qū)別是會(huì)跳過(guò)函數(shù)
c -> continue 過(guò)掉當(dāng)前斷點(diǎn)
通過(guò)匯編代碼的調(diào)試可以看出
jne j->jump
自旋鎖(while循環(huán))
自旋鎖屬于高級(jí)鎖:不睡覺syscall -> 系統(tǒng)函數(shù),睡覺去了
強(qiáng)引用thread無(wú)法解決線程的后續(xù)任務(wù)無(wú)法調(diào)用的問(wèn)題,使用runloop是為了讓線程保持激活狀態(tài)
線程同步的本質(zhì)是:多條線程不同時(shí)占用同一份資源,也就是只要按順序訪問(wèn)就不會(huì)有問(wèn)題。
信號(hào)量:控制最大并發(fā)數(shù)
synchronize是一個(gè)遞歸鎖,是對(duì)pthreadmutex的封裝
如何證明是遞歸鎖(如果可以遞歸調(diào)用,說(shuō)明是遞歸鎖)
lock和unlock之間的代碼叫臨界區(qū)
atomic
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
讀寫安全:多讀單寫
讀不會(huì)破壞以前的數(shù)據(jù)。
結(jié)果之所以出現(xiàn)問(wèn)題,就是因?yàn)榘藢?改)操作