線程是進程內假想的持有 cpu 使用權的執行單位,一個進程下能夠建立多個線程並行執行;使用多線程的程序稱爲多線程運行,從程序開始執行是運行的程序成爲主線程,除此以外以後生成的線程爲次線程或子線程。ios
多個線程操做某個實例時,沒有獲得錯誤的結果或實例時,那麼該類就稱爲線程安全。結果不能保證時,則稱爲非線程安全。git
通常狀況下,常數對象是線程安全的,變量對象不是線程安全的。程序員
要想使用多線程不出錯且高效執行,並行編程的知識必不可少,線程間的任務分配和信息交換、共享資源的互斥、與 GUI 的交互以及動畫顯示等,使用時都要格外當心。macos
NSThread 是蘋果官方提供的面向對象操做線程技術,簡單方便,能夠直接操做對象,不過須要本身控制線程的生命週期,在平時較少使用。初始化建立 NSThread 的方法有以下幾種:編程
/* 使用target對象的selector做爲線程的任務執行體,該selector方法最多能夠接收一個參數,該參數即爲argument */ - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); /* 使用block做爲線程的任務執行體 */ - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); /* 類方法,返回值爲void 使用一個block做爲線程的執行體,並直接啓動線程 上面的實例方法返回NSThread對象須要手動調用start方法來啓動線程執行任務 */ + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); /* 類方法,返回值爲void 使用target對象的selector做爲線程的任務執行體,該selector方法最多接收一個參數,該參數即爲argument 一樣的,該方法建立完縣城後會自動啓動線程不須要手動觸發 */ + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
// 得到主線程 + (NSThread *)mainThread; // 判斷是否爲主線程(對象方法) - (BOOL)isMainThread; // 判斷是否爲主線程(類方法) + (BOOL)isMainThread; // 得到當前線程 NSThread *current = [NSThread currentThread]; // 線程的名字——setter方法 - (void)setName:(NSString *)n; // 線程的名字——getter方法 - (NSString *)name; // 線程進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態 - (void)start; // 線程進入阻塞狀態 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 線程進入死亡狀態 + (void)exit;
// 在主線程上執行操做 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array; // equivalent to the first method with kCFRunLoopCommonModes // 在指定線程上執行操做 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0); - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); // 在當前線程上執行操做,調用 NSObject 的 performSelector:相關方法 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Grand Central Dispatch(GCD) 是 Apple 開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。它是一個在線程池模式的基礎上執行的併發任務。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。數組
一、建立隊列(串行隊列或併發隊列)
二、將任務追加到隊列中,系統根據任務類型執行任務(同步或者異步)安全
// 串行隊列的建立方法 dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL); // 併發隊列的建立方法 dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); // 主隊列的獲取方法 dispatch_queue_t queue = dispatch_get_main_queue(); // 全局併發隊列的獲取方法 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 同步執行任務建立方法 dispatch_sync(queue, ^{ // 這裏放同步執行任務代碼 }); // 異步執行任務建立方法 dispatch_async(queue, ^{ // 這裏放異步執行任務代碼 });
咱們能夠看到,GCD 有兩種建立任務的方法:同步或異步;三種隊列:併發隊列、串行隊列和主隊列,一共有六種的組合方式,咱們逐個進行分析:多線程
#pragma mark ------------------------GCD 基本使用(六種不一樣的組合) #pragma mark -----------------------異步執行主隊列:在主線程中串行執行任務 - (void)asyncMain { NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"asyncSerial---begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ for(int i=0;i<2;i++){// 任務1 [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"main---end"); } #pragma mark -----------------------同步執行主隊列 //在主線程中使用 同步執行主隊列,程序會出現死鎖 - (void)syncMain { NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"syncSerial---begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ for(int i=0;i<2;i++){// 任務1 [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"main---end"); } #pragma mark -----------------------異步執行串行對類:開啓新線程,在當前線程下串行執行任務,任務不作等待 - (void)asyncSerial { NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"asyncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ // 任務1 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"asyncSerial---end"); } #pragma mark -----------------------同步執行串行對類:不開啓新線程,在當前線程下串行執行任務 - (void)syncSerial { NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"syncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ // 任務1 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"syncSerial---end"); } #pragma mark -------------------------異步執行併發隊列:開啓多個線程,任務交替(同時)執行 - (void)asyncConcurrent { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"asyncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ // 任務1 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"asyncConcurrent---end"); } #pragma mark ------------------------同步執行併發隊列:不開啓新線程,執行完一個任務在執行下一個任務,由於只有一個線程 - (void)syncConcurrent { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ // 任務1 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2];//模擬耗時操做 NSLog(@"1-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務2 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"2-------%@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ // 任務3 for(int i=0;i<2;i++){ [NSThread sleepForTimeInterval:2]; NSLog(@"3-------%@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent---end"); }
/** * 線程間通訊 */ - (void)communication { // 獲取全局併發隊列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 獲取主隊列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(queue, ^{ // 異步追加任務 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 } // 回到主線程 dispatch_async(mainQueue, ^{ // 追加在主線程中執行的任務 [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程 }); }); }
咱們有時須要異步執行兩組操做,並且第一組操做執行完以後,才能開始執行第二組操做。這樣咱們就須要一個至關於柵欄同樣的一個方法將兩組異步執行的操做組給分割起來,固然這裏的操做組裏能夠包含一個或多個任務。這就須要用到dispatch_barrier_async方法在兩個操做組間造成柵欄。併發
- (void)barrier { dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ // 追加任務1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_async(queue, ^{ // 追加任務2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_barrier_async(queue, ^{ [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"barrier---%@",[NSThread currentThread]); // 打印當前線程 }); dispatch_async(queue, ^{ // 追加任務3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_async(queue, ^{ // 追加任務4 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"4---%@",[NSThread currentThread]); // 打印當前線程 } }); }
/** * 延時執行方法 dispatch_after */ - (void)after { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"asyncMain---begin"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2.0秒後異步追加任務代碼到主隊列,並開始執行 NSLog(@"after---%@",[NSThread currentThread]); // 打印當前線程 }); }
/** * 一次性代碼(只執行一次)dispatch_once */ - (void)once { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 只執行1次的代碼(這裏面默認是線程安全的) }); }
監聽 group 中任務的完成狀態,當全部的任務都執行完成後,追加任務到 group 中,並執行任務。app
/** * 隊列組 dispatch_group_notify */ - (void)groupNotify { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任務1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任務2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的異步任務一、任務2都執行完畢後,回到主線程執行下邊任務 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程 } NSLog(@"group---end"); }); }
暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成後,纔會往下繼續執行。
隊列組 dispatch_group_wait -(void)groupWait { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任務1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 } }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任務2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程 } }); // 等待上面的任務所有完成後,會往下繼續執行(會阻塞當前線程) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"group---end"); }
- (void)groupEnterAndLeave { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ // 追加任務1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 } dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ // 追加任務2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程 } dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的異步操做都執行完畢後,回到主線程. for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程 } NSLog(@"group---end"); }); // // 等待上面的任務所有完成後,會往下繼續執行(會阻塞當前線程) // dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // // NSLog(@"group---end"); }
使用Dispatch Semaphore 能夠實現線程同步,將異步執行任務轉換爲同步執行任務。
- (void)semaphoreSync { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程 NSLog(@"semaphore---begin"); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block int number = 0; dispatch_async(queue, ^{ // 追加任務1 [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程 number = 100; dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"semaphore---end,number = %zd",number); }
NSOperation、NSOperationQueue 是蘋果提供給咱們的一套多線程解決方案。實際上 NSOperation、NSOperationQueue 是基於 GCD 更高一層的封裝,徹底面向對象。可是比 GCD 更簡單易用、代碼可讀性也更高。
NSOperation 須要配合 NSOperationQueue 來實現多線程。由於默認狀況下,NSOperation 單獨使用時系統同步執行操做,配合 NSOperationQueue 咱們能更好的實現異步執行。
NSOperation 實現多線程的使用步驟分爲三步:
NSOperation 是個抽象類,不能用來封裝操做。咱們只有使用它的子類來封裝操做。咱們有三種方式來封裝操做。
使用子類 NSInvocationOperation
使用子類 NSBlockOperation
自定義繼承自 NSOperation 的子類,經過實現內部相應的方法來封裝操做。
/** * 使用子類 NSInvocationOperation */ - (void)useInvocationOperation { // 1.建立 NSInvocationOperation 對象 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; // 2.調用 start 方法開始執行操做 [op start]; } /** * 任務1 */ - (void)task1 { for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程 } }
/** * 使用子類 NSBlockOperation */ - (void)useBlockOperation { // 1.建立 NSBlockOperation 對象 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程 } }]; // 2.調用 start 方法開始執行操做 [op start]; }
NSBlockOperation 還提供了一個方法 addExecutionBlock:經過 addExecutionBlock: 就能夠爲 NSBlockOperation 添加額外的操做。
NSOperationQueue 一共有兩種隊列:主隊列、自定義隊列。其中自定義隊列同時包含了串行、併發功能。下邊是主隊列、自定義隊列的基本建立方法和特色。
// 主隊列獲取方法 隊列中代碼在主線程運行 NSOperationQueue *queue = [NSOperationQueue mainQueue]; // 自定義隊列建立方法 隊列中代碼在子線程運行 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
/** * 使用 addOperation: 將操做加入到操做隊列中 */ - (void)addOperationToQueue { // 1.建立隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.建立操做 // 使用 NSInvocationOperation 建立操做1 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil]; // 使用 NSInvocationOperation 建立操做2 NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil]; // 使用 NSBlockOperation 建立操做3 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程 } }]; [op3 addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程 } }]; // 3.使用 addOperation: 添加全部操做到隊列中 [queue addOperation:op1]; // [op1 start] [queue addOperation:op2]; // [op2 start] [queue addOperation:op3]; // [op3 start] }
最大併發操做數:maxConcurrentOperationCount
/** * 設置 MaxConcurrentOperationCount(最大併發操做數) */ - (void)setMaxConcurrentOperationCount { // 1.建立隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.設置最大併發操做數 queue.maxConcurrentOperationCount = 1; // 串行隊列 // queue.maxConcurrentOperationCount = 2; // 併發隊列 // queue.maxConcurrentOperationCount = 8; // 併發隊列 // 3.添加操做 [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程 } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程 } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程 } }]; [queue addOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程 } }]; }
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操做之間的依賴關係。經過操做依賴,咱們能夠很方便的控制操做之間的執行前後順序
例:好比說有 A、B 兩個操做,其中 A 執行完操做,B 才能執行操做。
- (void)addDependency { // 1.建立隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.建立操做 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程 } }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程 } }]; // 3.添加依賴 [op2 addDependency:op1]; // 讓op2 依賴於 op1,則先執行op1,在執行op2 // 4.添加操做到隊列中 [queue addOperation:op1]; [queue addOperation:op2];
- (void)communication { // 1.建立隊列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 2.添加操做 [queue addOperationWithBlock:^{ // 異步進行耗時操做 for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程 } // 回到主線程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 進行一些 UI 刷新等操做 for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模擬耗時操做 NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程 } }]; }]; }
把這一塊單獨提出來是由於不管是使用 NSThread、GCD、NSOperation 等,在多個地方異步同時調用同一方法,會形成結果不符合預期,也就是線程不安全。
線程不安全解決方案:能夠給線程加鎖,在一個線程執行該操做的時候,不容許其餘線程進行操做。iOS 實現線程加鎖有不少種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) 等等各類方式,@synchronized、 NSLock這兩種方式比較經常使用。
銀行取錢案例(例子是使用NSThread開闢的線程,給執行的代碼加鎖(或同步代碼塊),其餘兩種多線程方式同理)
- (void)getMoney { Account *account = [[Account alloc] init]; account.accountNumber = @"1603121434"; account.balance = 1500.0; NSThread *thread1 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)]; [thread1 setName:@"Thread1"]; NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)]; [thread2 setName:@"Thread2"]; [thread1 start]; [thread2 start]; } - (void)draw:(id)money { // 當多個線程同時操做的時候,會存在競爭條件,數據結果就沒法保證 // double drawMoney = [money doubleValue]; // //判斷餘額是否足夠 // if (self.balance >= drawMoney) // { // //當前線程睡1毫秒 // [NSThread sleepForTimeInterval:0.001]; // self.balance -= drawMoney; // NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance); // }else{ // //餘額不足,提示 // NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]); // } // 咱們對draw:方法添加了一個同步代碼塊,使用@synchronized包圍的代碼即爲同步代碼塊,同步代碼塊須要一個監聽器,咱們使用account對象自己做爲監聽器,由於是account對象產生的競爭條件,當執行同步代碼塊時須要先獲取監聽器,若是獲取不到則線程會被阻塞,當同步代碼塊執行完成則釋放監聽器 // @synchronized (self) { // double drawMoney = [money doubleValue]; // if (self.balance >= drawMoney) // { // //當前線程睡1毫秒 // [NSThread sleepForTimeInterval:1]; // self.balance -= drawMoney; // NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance); // }else{ // //餘額不足,提示 // NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]); // } // } // 咱們使用鎖機制,建立了一個NSLock類的鎖對象,lock方法用於獲取鎖,若是鎖被其餘對象佔用則線程被阻塞,unlock方法用於釋放鎖,以便其餘線程加鎖。 [self.lock lock]; double drawMoney = [money doubleValue]; if (self.balance >= drawMoney) { //當前線程睡1毫秒 [NSThread sleepForTimeInterval:1]; self.balance -= drawMoney; NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance); }else{ //餘額不足,提示 NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]); } [self.lock unlock]; }
本文所涉及的代碼:threads