iOS多線程方案
Pthread : 一套通用的純C語言的多線程API,適用于Unix\Linux\Windows等系統(tǒng),跨平臺(tái)\可移植,線程的生命周期需要程序員進(jìn)行管理.常在加鎖解鎖場(chǎng)景中使用
NSThread :使用OC語言,更加面向?qū)ο?需要程序源管理生命周期,相對(duì)于pthread簡單易用,可直接操作線程對(duì)象
GCD: 使用C語言實(shí)現(xiàn),旨在替代NSThread等線程技術(shù),充分利用設(shè)備的多核,自動(dòng)管理線程的生命周期.
Operation : OC語言,使用更加面向?qū)ο?自動(dòng)管理線程生命周期.是基于GCD的封裝,比GCD多了一下簡單易用的功能.
NSThread\GCD\NSOperation底層創(chuàng)建線程都用到pthread;


NSOperation和GCD
控制最大并發(fā)數(shù)
NSOperationQueue可以設(shè)置最大并發(fā)數(shù)量 (setMaxConcurrentOperationCount:該屬性需要在任務(wù)添加到隊(duì)列中之前進(jìn)行設(shè)置),maxConcurrentOperationCount默認(rèn)值是-1;如果值設(shè)為0,那么不會(huì)執(zhí)行任何任務(wù);如果值設(shè)為1,那么該隊(duì)列是串行的;如果大于1,那么是并行的.
GCD:可以使用dispatch_semaphore,信號(hào)量是一個(gè)整型值,有初始計(jì)數(shù)值,代表同時(shí)只允許多少條線程訪問資源,;可以接收通知信號(hào)和等待信號(hào)。當(dāng)信號(hào)量收到通知信號(hào)時(shí),計(jì)數(shù)+1;當(dāng)信號(hào)量收到等待信號(hào)時(shí),計(jì)數(shù)-1;如果信號(hào)量為0,線程會(huì)被阻塞,直到信號(hào)量大于0,才會(huì)繼續(xù)下去.
dispatch_queue_t concurrentWorkQueue = dispatch_queue_create("concurrentWorkQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(serialQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(concurrentWorkQueue, ^{
NSLog(@"thread-info:%@開始執(zhí)行任務(wù)%d",[NSThread currentThread],(int)i);
sleep(1);
NSLog(@"thread-info:%@結(jié)束執(zhí)行任務(wù)%d",[NSThread currentThread],(int)i);
dispatch_semaphore_signal(semaphore);});
});
}
NSLog(@"主線程...!");
NSOperration可以很容易管理各個(gè)操作之間的依賴關(guān)系(addDependency:),CGD可以通過Block的嵌套實(shí)現(xiàn)/dispatch_barrier_async/dispatch_semaphore_t實(shí)現(xiàn)較為復(fù)雜
NSOperration可以通過KVO監(jiān)控操作進(jìn)行的狀態(tài)(準(zhǔn)備、執(zhí)行中、完成、被取消),GCD無;
GCD 只支持FIFO 的隊(duì)列,而NSOperationQueue可以調(diào)整隊(duì)列的執(zhí)行順序(通過調(diào)整權(quán)重)。NSOperationQueue可以方便的管理并發(fā)、NSOperation之間的優(yōu)先級(jí)
GCD常用函數(shù)
GCD中有2個(gè)用來執(zhí)行任務(wù)的函數(shù), queue:隊(duì)列, block:任務(wù)
用同步的方式執(zhí)行任務(wù) dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
用異步的方式執(zhí)行任務(wù) dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
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ù)).
同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力;
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力.
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式,
并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行,
串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
使用sync函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù),會(huì)卡住當(dāng)前的串行隊(duì)列(產(chǎn)生死鎖)
隊(duì)列組的使用
異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3

多線程的安全隱患
1塊資源可能會(huì)被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問同一塊資源,比如多個(gè)線程訪問同一個(gè)對(duì)象、同一個(gè)變量、同一個(gè)文件,當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題.
解決方案:使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行),常見的線程同步技術(shù)是:加鎖.
iOS中的線程同步方案
OSSpinLock
OSSpinLock叫做”自旋鎖”,等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài),一直占用著CPU資源,目前已經(jīng)不再安全,可能會(huì)出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)問題.如果等待鎖的線程優(yōu)先級(jí)較高,它會(huì)一直占用著CPU資源,優(yōu)先級(jí)低的線程就無法釋放鎖.
#import <libkern/OSAtomic.h>
// 初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
// 嘗試加鎖(如果需要等待就不加鎖,直接返回false;如果不需要等待就加鎖,返回true)
bool result = OSSpinLockTry(&lock);
//加鎖
OSSpinLockLock(&lock);
// 解鎖
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,從iOS10開始才支持
從底層調(diào)用看,等待os_unfair_lock鎖的線程會(huì)處于休眠狀態(tài),并非忙等
#import <os/lock.h>
// 初始化
os_unfair_lock lock lock = OS_UNFAIR_LOCK_INIT
// 嘗試加鎖
os_unfair_lock_trylock(&lock)
// 加鎖
os_unfair_lock_lock(&lock)
// 解鎖
os_unfair_lock_unlock(&lock);
pthread_mutex
mutex叫做”互斥鎖”,等待鎖的線程會(huì)處于休眠狀態(tài)
#import <pthread.h>
// 初始化鎖屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&att,PTHREAD_MUTEX_NORMAL);
// 初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,&attr);
// 嘗試加鎖
pthread_mutex_trylock(&mutex);
// 加鎖
pthread_mutex_lock(&mutex);
// 解鎖
pthread_mutex_unlock(&mutex);
// 銷毀相關(guān)資源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);
pthread_mutex – 遞歸鎖
// 初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
pthread_mutex – 條件
//初始化鎖
pthread_mutex_t mutex;
// NULL代表使用默認(rèn)屬性
pthread_mutex_init(&mutex,NULL);
//初始化條件
pthread_cond_t condition;
pthread_cond_init(&condition,NULL);
// 等待條件(進(jìn)入休眠,放開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);
pthread_mutex--條件
//初始化鎖
pthread_mutex_t mutex;
// NULL代表使用默認(rèn)屬性
pthread_mutex_init(&mutex,NULL);
// 初始化條件
pthread_cont_t condition;
pthread_cond_init(&condition,NULL);
// 等待條件(進(jìn)入休眠,放開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);
NSLock
NSLock是對(duì)mutex普通鎖的封裝,
// 初始化鎖
NSLock *lock =[[NSLock alloc]init];
-(BOOL)tryLock;
-(BOOL)lockBeforeDate:(NSDate*)limit;
- (void)lock;
-(void)unlock;
NSRecursiveLock也是對(duì)mutex遞歸鎖的封裝,API跟NSLock基本一致
NSCondition
NSCondition是對(duì)mutex和cond的封裝,
-(void)wait;
-(BOOL)waitUntilDate:(NSDate*)limit;
-(void)signal;
-(void)broadcast;
NSConditionLock是對(duì)NSCondition的進(jìn)一步封裝,可以設(shè)置具體的條件值.
@interface NSConditionLock : NSObject <NSLocking>
- (instancetype)initWithCondition:(NSInterger)condition;
@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;
dispatch_semaphore
信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步.
// 信號(hào)量初始值
int value = 1;
// 初始化信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
// 如果信號(hào)量值 <=0 當(dāng)前線程就會(huì)進(jìn)入休眠等待(只到信號(hào)量的值>0)
// 如果信號(hào)量的值 > 0 就減1 然后往下執(zhí)行后面的代碼(相當(dāng)于線程加鎖)
dispatch_semaphore_wait (semaphore,DISPATCH_TIME_FOREVER);
// 讓信號(hào)量的值加1(相當(dāng)于線程解鎖)
dispatch_semaphore_signal(semaphore);
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ù)
})
@synchronized是對(duì)mutex遞歸鎖的封裝
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作
自旋鎖、互斥鎖比較
什么情況使用自旋鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間很短
加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生
CPU資源不緊張
多核處理器
什么情況使用互斥鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間較長
單核處理器
臨界區(qū)有IO操作
臨界區(qū)代碼復(fù)雜或者循環(huán)量大
臨界區(qū)競(jìng)爭(zhēng)非常激烈
atomic
atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖,它并不能保證使用屬性的過程是線程安全的
iOS中的讀寫安全方案
同一時(shí)間,只能有1個(gè)線程進(jìn)行寫的操作;同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作;同一時(shí)間,不允許既有寫的操作,又有讀的操作.OS中的實(shí)現(xiàn)方案有pthread_rwlock:讀寫鎖, dispatch_barrier_async:異步柵欄調(diào)用.
// 初始化鎖
pthread_rwlock_lock_t lock;
pthread_rwlock_init(&lock,NULL);
// 讀 枷鎖
pthread_rwlock_rdlock(&lock);
// 讀-嘗試枷鎖
pthread_rwlock_tryrylock(&lock);
// 寫-加鎖
pthread_rwlock_wrlock(&lock);
// 寫-嘗試加鎖
pthread_rwlock_trywrlock(&lock)
// 解鎖
pthread_rwlock_unlock(&lock);
// 銷毀
pthread_rwlock_destory(&lock);
dispatch_barrier_async
這個(gè)函數(shù)傳入的并發(fā)隊(duì)列必須是自己通過dispatch_queue_cretate創(chuàng)建的,如果傳入的是一個(gè)串行或是一個(gè)全局的并發(fā)隊(duì)列,那這個(gè)函數(shù)便等同于dispatch_async函數(shù)的效果
// 初始化隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("rw_queue",DISPATCH_QUEUE_CONCURRENT);
// 讀
dispatch_async(queue,^{
});
// 寫
dispatch_barrier_async(queue,^{});