文集
iOS開發之多線程(1)—— 概述
iOS開發之多線程(2)—— Thread
iOS開發之多線程(3)—— GCD
iOS開發之多線程(4)—— Operation
iOS開發之多線程(5)—— Pthreads
iOS開發之多線程(6)—— 線程安全與各種鎖
目錄
- 文集
- 版本
- 簡介
-
-
- 操作
- 同步 & 異步
- 隊列
- 串行 & 并行
-
- 基本用法
-
-
- 1. 不使用隊列
- 2. 使用隊列
-
-
- 2.1 四種添加操作(任務)方法
- 2.2 其他屬性方法
- 2.3 線程間通信
-
-
版本
Xcode 11.5
Swift 5.2.2
簡介
我們先來讨論相關理論知識點, 代碼部分統一放到後文.
Operation基于GCD封裝, 完全面向對象. 對應于GCD, Operation也有任務和隊列的概念, 隻不過在Operation中任務(block)被稱為操作(operation).
Operation支援以下關鍵功能:
- 操作之間添加依賴
- 使用KVO監聽操作的執行狀态
- 對操作進行優先級排序
- 取消操作
操作
- 在GCD中, 任務代碼隻能寫在block中, 并且需要放到隊列(dispatch queue)中去執行.
- 而在Operation中, 操作對象可以單獨執行, 也可以添加到隊列 (operation queue)去執行.
Operation是一個抽象類, 代表一個任務. 通常我們使用它的子類NSInvocationOperation或NSBlockOperation來編寫任務代碼. 當然也可以直接使用Operation, 不過需要重寫main方法, 在main裡面編寫任務代碼.
3種建立方式:
- NSInvocationOperation (swift不支援): 此類調用選擇器方法(selector), 在方法裡面編寫任務代碼.
- NSBlockOperation (swift對應BlockOperation): 此類采用block方式, 在block中編寫任務代碼.
- NSOperation (swift對應Operation): 需要重寫main方法, 在main裡面編寫任務代碼.
2種執行方式:
- 不添加到隊列, 手動調用operation的start方法.
- 添加到隊列, 系統自動調用start方法.
具體如何使用請看下小節.
同步 & 異步
在GCD中, 同步和異步分别對應dispatch_sync和dispatch_async方法.
在Operation中, 沒有這種方法.
蘋果文檔
如果計劃手動執行操作對象,而不是将其添加到隊列中,則可以将操作設計為以同步或異步方式執行。預設情況下,操作對象是同步的。當start()直接從代碼中調用同步操作的方法時,該操作将在目前線程中立即執行。
預設情況下,操作對象以同步方式執行-也就是說,它們在調用其start方法的線程中執行其任務。
為了獲得最佳性能,您應該将操作設計為盡可能異步,使應用程式在執行操作時可以自由地做其他工作。
- 如果不使用隊列, operation預設以同步方式執行. 但我們有辦法使之異步執行: 建立一個新線程, 然後在新線程裡面調用start方法.
- 如果使用隊列, 系統預設以異步方式執行. 但我們可以使用waitUntilAllOperationsAreFinished (operation queue方法)進行等待, 以確定操作完成後才繼續往下執行.
注意: 與GCD的同步執行不同, 這裡雖然設定了等待, 但是一般不在目前線程執行, 而是新開一個線程來執行, 但會保證隊列裡的所有任務完成後才繼續往下執行.
隊列
隊列 (Operation Queue)有兩種: 主隊列和非主隊列 (自定義隊列).
- 主隊列通過mainQueue獲得, 主隊列裡的任務都是放到主線程執行 (不包括使用addExecutionBlock:添加的額外操作, 因其可能在其他線程執行).
- 非主隊列 (自定義隊列) 即一般 alloc init 出來的隊列, 預設在子線程中異步執行. 通多設定最大并發數(maxConcurrentOperationCount)來控制隊列是串行還是并發.
添加操作(任務)到隊列有四種方式:
-
addOperation:
添加一個現有的Operation (或者其子類).
-
addOperations:waitUntilFinished:
可添加多個現有的Operation (或者其子類), 可設定等待所有操作完成後方可繼續往下執行.
-
addOperationWithBlock:
直接添加一個block
-
addBarrierBlock:
添加栅欄, 順帶一個任務. 等栅欄前的所有任務都執行完, 再執行本栅欄的任務, 起到隔離同步等待的目的.
串行 & 并行
主隊列是串行隊列. 自定義隊列預設是并發隊列, 但可通多設定最大并發數(maxConcurrentOperationCount)來控制隊列是串行還是并發.
maxConcurrentOperationCount
-1, 預設值, 并發隊列;
=0, 不執行任何操作;
=1, 串行隊列;
<0, 除-1預設值外, 其他負值均報錯;
>1, 并發隊列, 如果數值過大, 最終并發數由系統決定.
基本用法
1. 不使用隊列
OC
#pragma mark - NSInvocationOperation (調用selector方法)
// 使用 NSInvocationOperation
- (void)useInvocationOperation {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doInvocationOperation) object:nil];
[operation start];
}
// 任務
- (void)doInvocationOperation {
NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
}
//---------------------------------------------------------------------------------------------------
//log:
//-[ViewController doInvocationOperation], thread:<NSThread: 0x6000017ec2c0>{number = 1, name = main}
#pragma mark - NSBlockOperation (使用block)
// 使用 NSBlockOperation
- (void)useBlockOperation {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
}];
[operation start];
}
//---------------------------------------------------------------------------------------------------
//log:
//-[ViewController useBlockOperation]_block_invoke, thread:<NSThread: 0x600000760100>{number = 1, name = main}
#pragma mark - 自定義Operation (重寫main)
- (void)useCustomOperation {
MyOperation *operation = [[MyOperation alloc] init];
[operation start];
}
//---------------------------------------------------------------------------------------------------
//log:
//-[MyOperation main], thread:<NSThread: 0x600000618280>{number = 1, name = main}
MyOperation.m
@implementation MyOperation
- (void)main {
NSLog(@"%s, thread:%@", __func__, [NSThread currentThread]);
}
@end
在自定義Operation中, 調用start方法後, 系統會執行多項安全檢查, 最終會調用main方法.
Swift
//MARK: - 使用 BlockOperation (block)
@objc func useBlockOperation() {
let operation = BlockOperation.init {
print("\(#function), thread:\(Thread.current)")
}
operation.start()
}
//---------------------------------------------------------------------------------------------------
//log:
//useBlockOperation(), thread:<NSThread: 0x600003f78000>{number = 1, name = main}
//MARK: - 使用 自定義Operation (重寫main)
@objc func useCustomOperation() {
let operation = CustomOperation.init()
operation.start()
}
//---------------------------------------------------------------------------------------------------
//log:
//main(), thread:<NSThread: 0x600003ec8cc0>{number = 1, name = main}
class CustomOperation: Operation {
override func main() {
print("\(#function), thread:\(Thread.current)")
}
}
2. 使用隊列
2.1 四種添加操作(任務)方法
OC
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 1 addOperation:
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 thread:%@", [NSThread currentThread]);
}] ;
[operationQueue addOperation:op1];
// 2 addOperations:waitUntilFinished:
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 thread:%@", [NSThread currentThread]);
}] ;
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op3 thread:%@", [NSThread currentThread]);
}] ;
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op4 thread:%@", [NSThread currentThread]);
}] ;
[operationQueue addOperations:@[op2, op3, op4] waitUntilFinished:YES];
NSLog(@"queue finished");
// 3 addOperationWithBlock:
[operationQueue addOperationWithBlock:^{
NSLog(@"block thread:%@", [NSThread currentThread]);
}];
// 4 addBarrierBlock:
[operationQueue addBarrierBlock:^{
NSLog(@"barrier thread:%@", [NSThread currentThread]);
}];
//---------------------------------------------------------------------------------------------------
//log:
//op2 thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}
//op4 thread:<NSThread: 0x600002eaf180>{number = 4, name = (null)}
//op1 thread:<NSThread: 0x600002ed77c0>{number = 5, name = (null)}
//op3 thread:<NSThread: 0x600002eaf140>{number = 3, name = (null)}
//queue finished
//block thread:<NSThread: 0x600002ec7c80>{number = 6, name = (null)}
Swift
let operationQueue = OperationQueue.init()
// 1 addOperation(_ op: Operation)
let op1 = BlockOperation.init {
print("op1, thread:\(Thread.current)")
}
operationQueue.addOperation(op1)
// 2 addOperations(_ ops: [Operation], waitUntilFinished wait: Bool)
let op2 = BlockOperation.init {
print("op2, thread:\(Thread.current)")
}
let op3 = BlockOperation.init {
print("op3, thread:\(Thread.current)")
}
let op4 = BlockOperation.init {
print("op4, thread:\(Thread.current)")
}
operationQueue.addOperations([op2, op3, op4], waitUntilFinished: true)
// 3 addOperation(_ block: @escaping () -> Void)
operationQueue.addOperation {
print("block, thread:\(Thread.current)")
}
// 4 addBarrierBlock(_ barrier: @escaping () -> Void)
operationQueue.addBarrierBlock {
print("barrier, thread:\(Thread.current)")
}
//---------------------------------------------------------------------------------------------------
log:
op3, thread:<NSThread: 0x600002399200>{number = 4, name = (null)}
op1, thread:<NSThread: 0x6000023c34c0>{number = 5, name = (null)}
op2, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
op4, thread:<NSThread: 0x6000023d42c0>{number = 7, name = (null)}
block, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
barrier, thread:<NSThread: 0x600002398f80>{number = 6, name = (null)}
2.2 其他屬性方法
maxConcurrentOperationCount
- (void)setOperationCount {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSLog(@"start");
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 thread:%@", [NSThread currentThread]);
}] ;
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2 thread:%@", [NSThread currentThread]);
}] ;
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op3 thread:%@", [NSThread currentThread]);
}] ;
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op4 thread:%@", [NSThread currentThread]);
}] ;
/**
-1, 預設值, 并發隊列;
=0, 不執行任何操作;
=1, 串行隊列;
<0, 除-1預設值外, 其他負值均報錯;
>1, 并發隊列, 如果數值過大, 最終并發數由系統決定.
*/
operationQueue.maxConcurrentOperationCount = 1;
[operationQueue addOperations:@[op1, op2, op3, op4] waitUntilFinished:YES];
NSLog(@"end");
}
//---------------------------------------------------------------------------------------------------
log:
start
op1 thread:<NSThread: 0x600000b181c0>{number = 5, name = (null)}
op2 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
op3 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
op4 thread:<NSThread: 0x600000b75f80>{number = 6, name = (null)}
end
我們看到:
由于是串行隊列, 沒有添加依賴和設定優先級, 是以會按照1~4順序執行;
又因為設定了等待(waitUntilFinished:YES), 是以end最後才列印;
開不開新線程, 由系統決定.
添加依賴
設定優先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
取消隊列裡所有任務
2.3 線程間通信
- (void)callMainQueue {
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperationWithBlock:^{
NSLog(@"block, thread:%@", [NSThread currentThread]);
// call main queue
[NSOperationQueue.mainQueue addOperationWithBlock:^{
NSLog(@"main, thread:%@", [NSThread currentThread]);
}];
}];
}
//---------------------------------------------------------------------------------------------------
log:
block, thread:<NSThread: 0x600003ee0580>{number = 6, name = (null)}
main, thread:<NSThread: 0x600003ea0240>{number = 1, name = main}
demo
https://github.com/LittleLittleKang/KKThreadsDemo