GCD
- 同步/異步 和 串行/ 并發(fā)
- dispatch_barrier_async
- dispatch_group
同步/異步 和 串行/ 并發(fā)
- dispatch_sync(serial_queue, ^{//任務(wù)})妹蔽;
- dispatch_async(serial_queue, ^{//任務(wù)})乍楚;
- dispatch_sync(concurrent_queue, ^{//任務(wù)})丽柿;
- dispatch_async(concurrent_queue, ^{//任務(wù)});
同步串行
//頭條面試題
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
}
上面代碼的問題:
這段代碼的邏輯會(huì)產(chǎn)生死鎖甚牲,死鎖的原因隊(duì)列引起的循環(huán)等待.
viewDidLoad
的執(zhí)行過程中需要依賴于調(diào)用Block任務(wù)璃弄,而Block在隊(duì)列中的排列(即棧的先進(jìn)先出,因?yàn)锽lock任務(wù)在棧頂)導(dǎo)致他需要依賴viewDidLoad
執(zhí)行完畢窜觉,這種彼此依賴對方先完成谷炸,就導(dǎo)致了死鎖發(fā)生。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(serialQueue, ^{
[self doSomething];
});
}
代碼執(zhí)行正常禀挫,沒有問題:
//美團(tuán)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(global_queue, ^{
NSLog(@"2");
dispatch_sync(global_queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}//12345
只要同步去提交任務(wù)旬陡,無論隊(duì)列是串行還是并發(fā),最終都會(huì)在當(dāng)前線程去執(zhí)行
異步并發(fā)
//騰訊視頻
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(global_queue, ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
//輸出結(jié)果13
}
- (void)printLog {
NSLog(@"2");
}
performSelector:withObject:afterDelay:
因?yàn)樾枰?dāng)前線程有runloop去執(zhí)行timer事件特咆,但GCD底層是不創(chuàng)建runloop的沒有runloop季惩,即使時(shí)間是0录粱,方法也會(huì)失效腻格。所以結(jié)果是13
dispatch_barrier_async()
怎樣利用GCD實(shí)現(xiàn)多讀單寫?(滴滴啥繁,美團(tuán)面試題)
#import "UserCenter.h"
@interface UserCenter()
{
// 定義一個(gè)并發(fā)隊(duì)列
dispatch_queue_t concurrent_queue;
// 用戶數(shù)據(jù)中心, 可能多個(gè)線程需要數(shù)據(jù)訪問
NSMutableDictionary *userCenterDic;
}
@end
// 多讀單寫模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通過宏定義 DISPATCH_QUEUE_CONCURRENT 創(chuàng)建一個(gè)并發(fā)隊(duì)列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建數(shù)據(jù)容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步讀取指定數(shù)據(jù)
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 異步柵欄調(diào)用設(shè)置數(shù)據(jù)
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
@end
dispatch_group_async()
使用GCD實(shí)現(xiàn)三個(gè)需求:A菜职、B、C三個(gè)任務(wù)并發(fā)旗闽,完成后執(zhí)行任務(wù)D?(愛奇藝面試題)
#import "GroupObject.h"
@interface GroupObject()
{
dispatch_queue_t concurrent_queue;
NSMutableArray <NSURL *> *arrayURLs;
}
@end
@implementation GroupObject
- (id)init
{
self = [super init];
if (self) {
// 創(chuàng)建并發(fā)隊(duì)列
concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
arrayURLs = [NSMutableArray array];
}
return self;
}
- (void)handle
{
// 創(chuàng)建一個(gè)group
dispatch_group_t group = dispatch_group_create();
// for循環(huán)遍歷各個(gè)元素執(zhí)行操作
for (NSURL *url in arrayURLs) {
// 異步組分派到并發(fā)隊(duì)列當(dāng)中
dispatch_group_async(group, concurrent_queue, ^{
//根據(jù)url去下載圖片
NSLog(@"url is %@", url);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 當(dāng)添加到組中的所有任務(wù)執(zhí)行完成之后會(huì)調(diào)用該Block
NSLog(@"所有圖片已全部下載完成");
});
}
@end
NSOperation
需要和NSOperationQueue配合使用來實(shí)現(xiàn)多線程方案
優(yōu)勢和特點(diǎn):
- 添加任務(wù)依賴
- 任務(wù)執(zhí)行狀態(tài)控制
- 最大并發(fā)量
任務(wù)執(zhí)行狀態(tài)控制
- isReady - 當(dāng)前任務(wù)是否就緒
- isExecuting - 當(dāng)前任務(wù)是否處于正在執(zhí)行
- isFinished - 當(dāng)前任務(wù)是否完成
- isCancelled - 當(dāng)前任務(wù)是否取消
狀態(tài)控制
- 如果只重寫
main
方法酬核,底層控制變更任務(wù)執(zhí)行完成狀態(tài),以及任務(wù)退出适室。 - 如果只重寫
start
方法嫡意,自行控制任務(wù)狀態(tài)
系統(tǒng)是怎樣移除一個(gè)isFinished=YES
的NSOperation的?
系統(tǒng)是通過KVO監(jiān)聽實(shí)現(xiàn)的捣辆。
NSThread
啟動(dòng)流程
源碼來自gnustep-base
- (void) start
{
pthread_attr_t attr;
pthread_t thr;
if (_active == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on active thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_cancelled == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on cancelled thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
if (_finished == YES)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on finished thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
/* Make sure the notification is posted BEFORE the new thread starts.
*/
gnustep_base_thread_callback();
/* The thread must persist until it finishes executing.
*/
[self retain];
/* Mark the thread as active whiul it's running.
*/
_active = YES;
errno = 0;
pthread_attr_init(&attr);
/* Create this thread detached, because we never use the return state from
* threads.
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
/* Set the stack size when the thread is created. Unlike the old setrlimit
* code, this actually works.
*/
if (_stackSize > 0)
{
pthread_attr_setstacksize(&attr, _stackSize);
}
//指定線程的氣筒函數(shù)為nsthreadLauncher
if (pthread_create(&thr, &attr, nsthreadLauncher, self))
{
DESTROY(self);
[NSException raise: NSInternalInconsistencyException
format: @"Unable to detach thread (last error %@)",
[NSError _last]];
}
}
static void *nsthreadLauncher(void* thread)
{
NSThread *t = (NSThread*)thread;//獲取啟動(dòng)線程t
setThreadForCurrentThread(t);
#if __OBJC_GC__
objc_registerThreadWithCollector();
#endif
#if GS_WITH_GC && defined(HAVE_GC_REGISTER_MY_THREAD)
{
struct GC_stack_base base;
if (GC_get_stack_base(&base) == GC_SUCCESS)
{
int result;
result = GC_register_my_thread(&base);
if (result != GC_SUCCESS && result != GC_DUPLICATE)
{
fprintf(stderr, "Argh ... no thread support in garbage collection library\n");
}
}
else
{
fprintf(stderr, "Unable to determine stack base to register new thread for garbage collection\n");
}
}
#endif
/*
* Let observers know a new thread is starting.
*/
if (nc == nil)
{
nc = RETAIN([NSNotificationCenter defaultCenter]);
}
//發(fā)送通知
[nc postNotificationName: NSThreadDidStartNotification
object: t
userInfo: nil];
[t main];
[NSThread exit];
// Not reached
return NULL;
}
- (void) main
{
if (_active == NO)
{
[NSException raise: NSInternalInconsistencyException
format: @"[%@-$@] called on inactive thread",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd)];
}
[_target performSelector: _selector withObject: _arg];
}
多線程和鎖
鎖
iOS當(dāng)中都有哪些鎖蔬螟?
@synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t
@synchronized
- 一般在創(chuàng)建單例對象的時(shí)候使用,保證多線程環(huán)境下創(chuàng)建對象是唯一的
@synchronized
的作用是創(chuàng)建一個(gè)互斥鎖汽畴,保證此時(shí)沒有其它線程對self對象進(jìn)行修改旧巾。這個(gè)是objective-c的一個(gè)鎖定令牌,防止self
對象在同一時(shí)間內(nèi)被其它線程訪問忍些,起到線程的保護(hù)作用鲁猩。 一般在公用變量的時(shí)候使用,如單例模式或者操作類的static變量中使用罢坝。
指令@synchronized()
需要一個(gè)參數(shù)廓握。該參數(shù)可以使任何的Objective-C對象,包括self
嘁酿。這個(gè)對象就是互斥信號量隙券。他能夠讓一個(gè)線程對一段代碼進(jìn)行保護(hù),避免別的線程執(zhí)行該段代碼痹仙。
互斥鎖使用格式
@synchronized(鎖對象){ //需要鎖定的代碼 }
鎖定一份代碼只用1把鎖是尔,用多把鎖是無效的。
互斥鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的CPU資源
線程同步
多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖开仰,就是使用了線程同步技術(shù)拟枚。
例如:一個(gè)電影院薪铜,有3個(gè)售票員。一場電影的總數(shù)量固定恩溅。3個(gè)售票員售票時(shí)隔箍,要判斷是非還有余票。
#import "ViewController.h"
@interface ViewController ()
/** 售票員01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票員02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票員03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的總數(shù) */
@property (nonatomic, assign) NSInteger ticketCount;
/** 鎖對象 */
//@property (nonatomic, strong) NSObject *locker;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// self.locker = [[NSObject alloc] init];
self.ticketCount = 100;
self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"售票員01";
self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"售票員02";
self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"售票員03";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
- (void)saleTicket
{
while (1) {
@synchronized(self) {
// 先取出總數(shù)
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票脚乡,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"票已經(jīng)賣完了");
break;
}
}
}
}
@end
atomic
- 修飾屬性的關(guān)鍵字
- 對被修飾對象進(jìn)行原子操作(不負(fù)責(zé)使用)
@property(atomic) NSMutableArray * array;
self.array = [NSMutableArray array];//?賦值是線程安全的
[self.array addObject:obj];//?操作數(shù)組是不能保證線程安全的
atomic和nonatomic的對比
1蜒滩、atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。
2奶稠、atomic:系統(tǒng)生成的 getter/setter 會(huì)保證 get俯艰、set 操作的完整性,不受其他線程影響锌订。getter 還是能得到一個(gè)完好無損的對象(可以保證數(shù)據(jù)的完整性)竹握,但這個(gè)對象在多線程的情況下是不能確定的,比如上面的例子辆飘。
也就是說:如果有多個(gè)線程同時(shí)調(diào)用setter的話啦辐,不會(huì)出現(xiàn)某一個(gè)線程執(zhí)行完setter全部語句之前,另一個(gè)線程開始執(zhí)行setter情況蜈项,相當(dāng)于函數(shù)頭尾加了鎖一樣芹关,每次只能有一個(gè)線程調(diào)用對象的setter方法,所以可以保證數(shù)據(jù)的完整性紧卒。
atomic所說的線程安全只是保證了getter和setter存取方法的線程安全侥衬,并不能保證整個(gè)對象是線程安全的。
3常侦、nonatomic:就沒有這個(gè)保證了浇冰,nonatomic返回你的對象可能就不是完整的value。因此聋亡,在多線程的環(huán)境下原子操作是非常必要的肘习,否則有可能會(huì)引起錯(cuò)誤的結(jié)果。但僅僅使用atomic并不會(huì)使得對象線程安全坡倔,我們還要為對象線程添加lock來確保線程的安全漂佩。
4、nonatomic的速度要比atomic的快罪塔。
5投蝉、atomic與nonatomic的本質(zhì)區(qū)別其實(shí)也就是在setter方法上的操作不同
atomic對象setter和getter方法的實(shí)現(xiàn):
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
OSSpinLock - 自旋鎖
- 循環(huán)等待詢問,不釋放當(dāng)前資源
- 用于輕量級數(shù)據(jù)訪問征堪。比如簡單的
int
值+1/-1操作
runtime中有使用到瘩缆,進(jìn)行引用計(jì)數(shù)的+1/-1操作
NSLock
一般用于解決一些細(xì)粒度的線程同步問題,用來保證各個(gè)線程互斥來進(jìn)入自己的臨界區(qū)佃蚜。
//螞蟻金服
- (void)methodA {
[nslock lock];
[self methodB];
[nslock unlock];
}
- (void)methodB {
[nslock lock];
//操作邏輯
[nslock unlock];
}
以上代碼的問題:死鎖
methodA
中在某一線程的調(diào)用下庸娱,已經(jīng)對線程加了lock
着绊,如果在methodB
再次調(diào)用lock
,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除熟尉,這樣就導(dǎo)致了死鎖归露,線程被阻塞住了。
NSRecursiveLock - 遞歸鎖
NSRecursiveLock實(shí)際上定義的是一個(gè)遞歸鎖斤儿,這個(gè)鎖可以被同一線程多次請求剧包,而不會(huì)引起死鎖。這主要是用在循環(huán)或遞歸操作中往果。
遞歸鎖會(huì)跟蹤它被lock的次數(shù)疆液。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達(dá)到這種平衡棚放,鎖最后才能被釋放枚粘,以供其它線程使用。
上面的問題可以通過使用遞歸鎖進(jìn)行解決:
- (void)methodA {
[recursiveLock lock];
[self methodB];
[recursiveLock unlock];
}
- (void)methodB {
[recursiveLock lock];
//操作邏輯
[recursiveLock unlock];
}
dispatch_semaphore_t
dispatch_semaphore_create(long value); // 創(chuàng)建信號量
dispatch_semaphore_signal(dispatch_semaphore_t semaphore); // 發(fā)送信號量
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout); // 等待信號量
-
dispatch_semaphore_create(long value);
和GCD的group等用法一致飘蚯,這個(gè)函數(shù)是創(chuàng)建一個(gè)dispatch_semaphore_類型的信號量,并且創(chuàng)建的時(shí)候需要指定信號量的大小福也。 -
dispatch_semaphore_signal(dispatch_semaphore_t semaphore);
發(fā)送信號量局骤。該函數(shù)會(huì)對信號量的值進(jìn)行加1操作。 -
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout);
等待信號量暴凑。如果信號量值為0峦甩,那么該函數(shù)就會(huì)一直等待,也就是不返回(相當(dāng)于阻塞當(dāng)前線程)现喳,直到該函數(shù)等待的信號量的值大于等于1凯傲,該函數(shù)會(huì)對信號量的值進(jìn)行減1操作,然后返回嗦篱。
dispatch_semaphore_create(long value);
在底層會(huì)生成:
struct semaphore {
int value; //信號量的值
List <thread>; //一個(gè)線程列表
}
dispatch_semaphore_wait(dispatch_semaphore_t semaphore, dispatch_time_t timeout)
在底層的實(shí)現(xiàn)邏輯大致如下:
dispatch_semaphore_wait()
{
S.value -= 1;
if S.value < 0 then Block(S.list);//阻塞-主動(dòng)行為
}
dispatch_semaphore_signal()
底層實(shí)現(xiàn)邏輯大致如下:
dispatch_semaphore_signal()
{
S.value += 1;
if S.value <= 0 then wakeup(S.list);//喚醒-被動(dòng)行為
}
通常等待信號量和發(fā)送信號量的函數(shù)是成對出現(xiàn)的冰单。并發(fā)執(zhí)行任務(wù)時(shí)候,在當(dāng)前任務(wù)執(zhí)行之前灸促,用dispatch_semaphore_wait
函數(shù)進(jìn)行等待(阻塞)诫欠,直到上一個(gè)任務(wù)執(zhí)行完畢后且通過dispatch_semaphore_signal
函數(shù)發(fā)送信號量(使信號量的值加1),dispatch_semaphore_wait
函數(shù)收到信號量之后判斷信號量的值大于等于1浴栽,會(huì)再對信號量的值減1荒叼,然后當(dāng)前任務(wù)可以執(zhí)行,執(zhí)行完畢當(dāng)前任務(wù)后典鸡,再通過dispatch_semaphore_signal
函數(shù)發(fā)送信號量(使信號量的值加1)被廓,通知執(zhí)行下一個(gè)任務(wù)......如此一來,通過信號量萝玷,就達(dá)到了并發(fā)隊(duì)列中的任務(wù)同步執(zhí)行的要求嫁乘。
總結(jié)
怎樣用GCD事項(xiàng)多讀單寫英遭?
iOS系統(tǒng)為我們提供的幾種多線程技術(shù)各自的特點(diǎn)是怎樣的?
在iOS系統(tǒng)當(dāng)中主要提供了三種多線程技術(shù)亦渗,分別為GCD, NSOperation, NSThread挖诸,一般使用GCD來解決一些簡單的線程同步法精,包括一些子線程的分派搂蜓,包括實(shí)現(xiàn)一些例如多讀單寫這種場景的問題的解決帮碰。對于NSOperation殉挽,比如AFNetworking斯碌,SDWebimageView傻唾,他們內(nèi)部都會(huì)涉及到
NSOperation
,由于他的特點(diǎn)是可以方便我們對任務(wù)的狀態(tài)進(jìn)行控制冠骄,包括可以控制依賴的添加和移除依賴凛辣。對于NSThread往往我們用他來實(shí)現(xiàn)同一個(gè)常駐線程。
-
NSOperation
對象在Finished
之后是怎樣從隊(duì)列中移除掉的蝙砌?
NSOperation
對象在Finished
之后,會(huì)在內(nèi)部用KVO的形式通知NSOperationQueue
達(dá)到對NSOperation
的移除
你都用過哪些鎖肚邢?結(jié)合實(shí)際談?wù)勀闶窃鯓邮褂玫模?/p>