NSOperation
- NSOperation有兩個常用子類用于創(chuàng)建線程操作:NSInvocationOperation和NSBlockOperation,兩種方式本質(zhì)沒有區(qū)別,但是是后者使用Block形式進(jìn)行代碼組織,使用相對方便。
-(void)loadImageWithMultiThread{
/*創(chuàng)建一個調(diào)用操作
object:調(diào)用方法參數(shù)
*/
NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//創(chuàng)建完NSInvocationOperation對象并不會調(diào)用,它由一個start方法啟動操作,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用,一般不會這么操作,而是添加到NSOperationQueue中
// [invocationOperation start];
//創(chuàng)建操作隊列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//注意添加到操作隊后,隊列會開啟一個線程執(zhí)行此操作
[operationQueue addOperation:invocationOperation];
}
//直接使用操隊列添加操作
[operationQueue addOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
或
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//創(chuàng)建操作隊列
[operationQueue addOperation:blockOperation];
- 創(chuàng)建完NSInvocationOperation對象并不會調(diào)用,它由一個start方法啟動操作,但是注意如果直接調(diào)用start方法,則此操作會在主線程中調(diào)用,一般不會這么操作,而是添加到NSOperationQueue中
- 每個NSOperation可以設(shè)置依賴線程:
[blockOperation addDependency:lastBlockOperation]; - 主線程更新UI
//更新UI界面,此處調(diào)用了主線程隊列的方法(mainQueue是UI主線程)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateImageWithData:data andIndex:i];
}];
- 自定義NSOperation 參考
- 自定義串行NSOperation
如果是使用start調(diào)用制定串行的NSOperation,是串行執(zhí)行的,如果是吧自定義的串行NSOperation放到NSOperationQueue中則是并行的。 - 自定義并行NSOperation
- start方法:該方法必須實現(xiàn),
- main:該方法可選,如果你在start方法中定義了你的任務(wù),則這個方法就可以不實現(xiàn),但通常為了代碼邏輯清晰,通常會在該方法中定 義自己的任務(wù)
- isExecuting isFinished 主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知
- isConcurrent :必須覆蓋并返回YES;
- 自定義串行NSOperation
- NSOperationQueue創(chuàng)建的其他隊列同時具有串行、并發(fā)功能, maxConcurrentOperationCount,叫做最大并發(fā)數(shù)。
- maxConcurrentOperationCount=-1時默認(rèn)是并發(fā)隊列。
- maxConcurrentOperationCount>1時是并發(fā)隊列。
- maxConcurrentOperationCount=1時是串行隊列
- 如說有A、B兩個操作,其中A執(zhí)行完操作,B才能執(zhí)行操作,那么就需要讓B依賴于A。
[op2 addDependency:op1]; // 讓op2 依賴于 op1,則先執(zhí)行op1,在執(zhí)行op2
GCD
- 在GDC中一個操作是多線程執(zhí)行還是單線程執(zhí)行取決于當(dāng)前隊列類型和執(zhí)行方法,只有隊列類型為并行隊列并且使用異步方法執(zhí)行時才能在多個線程中執(zhí)行。
- 串行隊列可以按順序執(zhí)行,并行隊列的異步方法無法確定執(zhí)行順序。
- UI界面的更新最好采用同步方法,其他操作采用異步方法。
- dispatch_get_main_queue(),這個是主線程,是Serial Dispatch Queue
- dispatch_sync dispatch_async的區(qū)別
dispatch_asycn函數(shù)的”asycn”意味著異步,就是將block追加到指定的queue中后,dispatch_asycn函數(shù)立即返回,不會等待執(zhí)行的結(jié)果。
dispatch_queue_t queue = dispatch_queue_create("com.example.myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"block開始 queue:%@", [NSThread currentThread]);
for(int i=0; i++ ; i<100) {
}
NSLog(@"block結(jié)束 queue:%@", [NSThread currentThread]);
});
NSLog(@"主線程 queue:%@", [NSThread currentThread]);
打印結(jié)果為:
block開始 queue:<NSThread: 0x60800006aa00>{number = 3, name = (null)}
主線程 queue:<NSThread: 0x604000067bc0>{number = 1, name = main}
block結(jié)束 queue:<NSThread: 0x60800006aa00>{number = 3, name = (null)}
dispatch_sycn函數(shù)意味著同步,也就是將block函數(shù)追加到queue中后,在block任務(wù)執(zhí)行完之前,dispatch_sycn函數(shù)會一直等待。其實還是在主線程執(zhí)行的。
dispatch_queue_t queue = dispatch_queue_create("com.example.myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"block開始 queue:%@", [NSThread currentThread]);
for(int i=0; i++ ; i<100) {}
NSLog(@"block結(jié)束 queue:%@", [NSThread currentThread]);
});
NSLog(@"主線程 queue:%@", [NSThread currentThread]);
打印結(jié)果為:
block開始 queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
block結(jié)束 queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
主線程 queue:<NSThread: 0x6080000768c0>{number = 1, name = main}
- 串行隊列 + 異步執(zhí)行 所有隊列中的事務(wù)都是在一個子線程中執(zhí)行
- (void)serialQueueaSync {
NSLog(@"serialQueueSync begin-------%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("serialQueueSync", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"1-------%@", [NSThread currentThread]);
}});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"2-------%@", [NSThread currentThread]);
}});
dispatch_async(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"3-------%@", [NSThread currentThread]);
} });
NSLog(@"serialQueueSync end-------%@", [NSThread currentThread]);
}
打印結(jié)果為:
serialQueueSync begin-------<NSThread: 0x60c0000628c0>{number = 1, name = main}
serialQueueSync end-------<NSThread: 0x60c0000628c0>{number = 1, name = main}
1-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
1-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
2-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
2-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
3-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
3-------<NSThread: 0x60c0002711c0>{number = 4, name = (null)}
- 串行隊列 + 同步執(zhí)行 所有隊列中的事務(wù)都是主線程執(zhí)行
- (void)serialQueueSync {
NSLog(@"serialQueueSync begin-------%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("serialQueueSync", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"1-------%@", [NSThread currentThread]);
}});
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"2-------%@", [NSThread currentThread]);
} });
dispatch_sync(queue, ^{
for (int i=0; i<2; i++) {
NSLog(@"3-------%@", [NSThread currentThread]);
} });
NSLog(@"serialQueueSync end-------%@", [NSThread currentThread]);
}
打印結(jié)果:
begin-------<NSThread: 0x608000060540>{number = 1, name = main}
1-------<NSThread: 0x608000060540>{number = 1, name = main}
1-------<NSThread: 0x608000060540>{number = 1, name = main}
2-------<NSThread: 0x608000060540>{number = 1, name = main}
2-------<NSThread: 0x608000060540>{number = 1, name = main}
3-------<NSThread: 0x608000060540>{number = 1, name = main}
3-------<NSThread: 0x608000060540>{number = 1, name = main}
serialQueueSync end-------<NSThread: 0x608000060540>{number = 1, name = main}
- 死鎖
在串行隊列中執(zhí)行同步操作會發(fā)生死鎖,例如在主線程中執(zhí)行如下代碼,會在打印完第一個log后,死鎖。
NSLog(@"hello world0");
//因為是同步執(zhí)行,所以
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello world1");
});
NSLog(@"hello world2");
同理,如下代碼也會發(fā)生死鎖,如下代碼只會影響serialQueue內(nèi)部發(fā)生死鎖,不會阻塞主線程。
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);
NSLog(@"main begin");
dispatch_async(serialQueue, ^{
NSLog(@"外層begin");
dispatch_sync(serialQueue, ^{
NSLog(@"hello world");
});
NSLog(@"外層end");
});
NSLog(@"main end");
運(yùn)行結(jié)果:
main begin
main end
外層begin
- dispatch group 經(jīng)常會碰到這種情況,想要在加入到Dispatch Queue中的多個block任務(wù)都執(zhí)行完后取執(zhí)行其他任務(wù),如果使用的是串行隊列,只要將所有的block任務(wù)加入到串行隊列并在最后追加其他任務(wù)即可;如果使用的是并行隊列或者有多個DIspatch Queue時,可以使用Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1 begin");
for(int i=0; i<1000; i++){}
NSLog(@"block1 end");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2 begin");
for(int i=0; i<1000; i++){}
NSLog(@"block1 end");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3 begin");
for(int i=0; i<1000; i++){}
NSLog(@"block3 end");
});
dispatch_group_notify(group, dispatch_get_main_queue()/*queue*/, ^{
NSLog(@"done");
});
NSLog(@"testGroup END");
打印結(jié)果
block1 begin
testGroup END
block2 begin
block3 begin
block1 end
block3 end
block1 end
done
dispatch_group_notify(dispatch_group_t group, <#dispatch_queue_t queue#>, <#^(void)block#>)是等到之前與group聯(lián)系過的block執(zhí)行完后會把這個block提交到queue中。
無論向什么樣的Dispatch Queue中追加任務(wù),使用Dispatch Group都可以監(jiān)視這些任務(wù)的結(jié)束。
- dispatch_barrier_asycn
dispatch_barrier_asycn會等到追加到queue中的block任務(wù)執(zhí)行完后,再將指定的block任務(wù)追加到queue中,然后等到指定的block執(zhí)行完后,queue才恢復(fù)一般的動作,之后追加的block任務(wù)才開始執(zhí)行。
NSMutableArray * array = @[@"1"].mutableCopy;
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"block1 = %@", array[0]);
//讀取處理block1
});
dispatch_async(queue, ^{
NSLog(@"block2 = %@", array[0]);
//讀取處理block2
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"block3 = %@", array[0]);
//讀取處理block3
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch barrier begin");
[array replaceObjectAtIndex:0 withObject:@"2"];
NSLog(@"dispatch barrier end");
});
dispatch_async(queue, ^{
NSLog(@"block4 = %@", array[0]);
});
dispatch_barrier_async會等上面的block1, bolock2,block3 執(zhí)行完成后,再執(zhí)行自己的block,然后才是執(zhí)行block4,運(yùn)行結(jié)果:
block2 = 1
block1 = 1
block3 = 1
dispatch barrier begin
dispatch barrier end
block4 = 2
- dispatch_after
dispatch_after函數(shù)并不是在指定的時間后執(zhí)行block,而是在指定的時間將block添加到queue中。
線程同步
- @synchronized代碼塊
使用@synchronized解決線程同步問題相比較NSLock要簡單一些,日常開發(fā)中也更推薦使用此方法。首先選擇一個對象作為同步對象(如果成員變量或者屬性為同步對象,一般使用self),然后將”加鎖代碼”(爭奪資源的讀取、修改代碼)放到代碼塊中。@synchronized中的代碼執(zhí)行時先檢查同步對象是否被另一個線程占用,如果占用該線程就會處于等待狀態(tài),直到同步對象被釋放。 - 信號量機(jī)制dispatch_semaphore 淺談GCD中的信號量,條件鎖NSCondition
- 轉(zhuǎn)換異步block為同步方式運(yùn)行
- 互斥鎖 NSLock、遞歸鎖NSRecursiveLock:同一線程可多次加鎖,不會造成死鎖.
- 自旋鎖 OSSpinLock。自旋鎖與互斥鎖有點(diǎn)類似,只是自旋鎖不會引起調(diào)用者睡眠,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持,調(diào)用者就一直循環(huán)在那里看是否該自旋鎖的保持者已經(jīng)釋放了鎖,"自旋"一詞就是因此而得名。由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖。不再安全的 OSSpinLock
- noatomic 只對set get方法起作用,
- pthread_mutex(YYCache)
除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息稱,蘋果在新系統(tǒng)中已經(jīng)優(yōu)化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并沒有那么大了。
參考文檔
GCD常用方法總結(jié)
起底多線程同步鎖(iOS)
NSRecursiveLock遞歸鎖的使用
iOS多線程-各種線程鎖的簡單介紹