原創內容,轉載請注明出處:
http://www.lxweimin.com/p/ac11fe7ef78c
前言
多線程的東西好久沒弄過了,項目里面用不到,慢慢都忘記了,最近不太忙,把這些東西重新拿出來弄一下,做下復習.這次先把api的東西捋一遍,下次再詳說比較深層點的東西.
NSThead
先說幾個點
- NSThread 創建一個線程相對來說還是比較方便的
- NSThread 管理多個線程比較困難,所以不太推薦使用
- 蘋果建議,現在推薦用GCD和NSOperation,也就是說其他的都不讓(建議)用了
- [NSTread currentThread] 跟蹤任務所在線程,適用于NSTread,NSOperation,和GCD
- 使用NSThread 的線程,不會自動添加autoreleasepool
iOS中隱式中創建線程的方法(NSObject的方法):
//(NSObject)
//1.waitUntilDone:在主線程中運行方法,wait表示是否阻塞這個方法的調用,如果為yes則等待主線程中的運行方法結束。一般可用于在子線程中調用ui方法。此規則針對其他線程也適用。
//2. modes:關于這個參數,暫時不做講解,下次再說,這里先說一下,如果modes參數為kCFRunLoopCommonModes的話可以結局滑動過程中圖片賦值引起的頁面卡頓問題,等.
//3.此方法不能夠自動回收線程,如果并發數量多,會建立大量子線程。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
//延遲執行這里,用到runloop的東西,不太懂runloop的同學,可以去學習一下,我下次也會說到
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
//取消線程隊列中還沒有執行的方法
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
NSThread各個屬性的意義
@property double threadPriority //線程優先級(double)0.0~1.0,默認0.5,優先級和執行順序成正比
@property (nullable, copy) NSString *name //線程名字
@property NSUInteger stackSize //線程棧大小,默認主線程1m ,子線程512k,次屬性可讀寫,但是寫入大小必須為4k的倍數,最小為16k
@property (readonly) BOOL isMainThread // 是否是主線程
@property (readonly, getter=isExecuting) BOOL executing //是否正在執行
@property (readonly, getter=isFinished) BOOL finished //是否已經完成
@property (readonly, getter=isCancelled) BOOL cancelled //是否已經取消
NSThead類方法,作用域:當前線程
+ (NSThread *)currentThread; //返回當前線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; //開辟一個新的線程
+ (void)sleepUntilDate:(NSDate *)date;//休眠到什么時候(具體日期)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //休眠一段時間單位秒
+ (void)exit; //結束當前線程
+ (double)threadPriority; //返回當前線程優先級
+ (BOOL)setThreadPriority:(double)p; //設置當前線程優先級 0.0~1.0
+ (NSArray<NSNumber *> *)callStackReturnAddresses //返回當前線程訪問的堆棧信息
+ (NSArray<NSString *> *)callStackSymbols //返回一堆十六進制的地址
+ (BOOL)isMainThread //返回當前線程是否是主線程
+ (NSThread *)mainThread //返回主線程
NSThead實例方法
- (instancetype)init //總共五個實例方法中沒有給NSThread 加 selector 的方法,那這個方法是干什么用的呢?目測是用來繼承然后重寫main方法來用的
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument //通過selector初始化
- (void)main //主方法,用于子類繼承重寫
- (void)start //開始線程
- (void)cancel //取消線程
一些注意點:
- 在子類重寫父類的方法中,start 方法先于main方法執行;
- 線程中的自動釋放池:
@autoreleasepool{}自動釋放池
主線程中是有自動釋放池的,使用gcd 和nsoperation 也會自動添加自動釋放池,但是nsthread 和nsobject 不會,如果在后臺線程中創建了autorelease的對象,需要使用自動釋放池,否則會出現內存泄露
當自動釋放池銷毀時,對池中的所有對象發送release 消息,清空自動釋放池
當所有autorelease 的對象,在出了作用域之后,會自動添加到(最近一次創建的自動釋放池中)自動釋放池中
NSOperation
基本屬性:
@property (nullable, copy) NSString *name //該操作的名稱
@property (readonly, getter=isCancelled) BOOL cancelled; // 該操作是否已經取消
@property (readonly, getter=isExecuting) BOOL executing; //該操作是否正在執行
@property (readonly, getter=isFinished) BOOL finished; //該操作是否已經完成
@property (readonly, getter=isConcurrent) BOOL concurrent; //該操作是否是并行操作
@property (readonly, getter=isAsynchronous) BOOL asynchronous //該操作是否是異步的
@property (readonly, copy) NSArray<NSOperation *> *dependencies; //和該操作有關的依賴關系
@property NSOperationQueuePriority queuePriority; //該操作的優先級
@property (nullable, copy) void (^completionBlock)(void) //該操作的完成回調block
@property double threadPriority // 該操作的優先級
實例方法:
- (void)start; //開始方法,被加入隊列或手動開始的時候會被調用
- (void)main; // 隊列的主方法,start 后執行,子類應重寫此方法相比于start方法
- (void)cancel; // 取消操作
- (void)addDependency:(NSOperation *)op; //添加依賴,依賴只是設置先后執行的順序關系,可以跨隊列依賴,不可以循環依賴。添加依賴以后會順序執行任務,但是不一定開一個線程,可能會開多個線程,但是不會太多。
- (void)removeDependency:(NSOperation *)op; //移除依賴關系
- (void)waitUntilFinished //
子類NSInvocationOperation
屬性:
@property (readonly, retain) NSInvocation *invocation; //此隊列的NSInvocation參數,注意此參數只讀
@property (nullable, readonly, retain) id result; //
實例方法:
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;//通過@selector 初始化
- (instancetype)initWithInvocation:(NSInvocation *)inv //通過NSInvocation初始化
一些注意點:
- 調用方式,可以調用start方法,也可以添加到隊列調用操作但是:調用start和添加到隊列不可以同時使用
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(test1)
object:@"Invocation"];
添加到隊列方式啟動
NSOperationQueue *queue = nil;
[queue addOperation:op];
調用start方式啟動
[op start];
- 調用start 開啟任務,會在
當前線程
開啟任務,如果幾個操作一起調用start 則在當前線程串行
完成任務 - 把操作放到隊列queue中開啟任務會在
其他線程
去執行任務
子類NSInvocationOperation
屬性:
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks; //此操作的所有blcok
類方法:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;// 通過block初始化操作,該block沒有參數沒有返回值,
實例方法:
- (void)addExecutionBlock:(void (^)(void))block;//添加block,該block沒有參數沒有返回值
一些注意點:
- 關于循環引用:
(前者)我們平時用的適合基本類和隊列和操作的關系為 self -(持有)->queue -(持有)->block -(持有)->self 這種關系不會造成循環引用
(后者)self-(持有)->block -(持有)->self 這種會造成循環引用
- 下面分別解釋兩者區別,以下就稱之為前者和后者:前者為什么不會造成循環引用?因為self持有queue ,然后queue 持有block ,因為queue 執行每個block都是一個線程,線程是一條直線,這個線程執行完了這個block也就隨之消失了,所以這時候block所引用的self也會隨之消失.當queue里的任務執行完了,那持有關系就會變成 self 持有queue而已,后面就沒有了.然而后者.self持有block ,block持有self,任務執行完后block還在,那么它引用的self也還在,這時候就造成了循環引用,但是如果給前者的線程都加上runloop(也就是把線程的線性改為環性,也就是不讓線程執行完任務就死掉。主線程系統默認創建runloop,子線程默認不創建)那么前者也會造成循環引用.
(1).單純在操作中使用(也就是操作和self之前沒有引用關系)self不會造成循環引用
(2).如果self對象持有操作對象的引用,同時操作對象中又直接訪問了self時會造成循環引用.
(3).只有self直接強引用block 才會出現循環引用.
(4).block 的管理以及線程的創建和銷毀是由隊列負責的,直接再block中使用self沒有關系.
(5).weakSelf 也不是可以在任何block中都能使用,詳情去訊息runloop.
NSOperationQueue
屬性:
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations; //當前隊列的所有操作
@property (readonly) NSUInteger operationCount;// 當前隊列的所有操作的數量
@property NSInteger maxConcurrentOperationCount; //設置最大并發量
@property (getter=isSuspended) BOOL suspended; //掛起任務和開始任務,掛起的隊列不會影響已執行中和執行完的任務,隊列暫停再添加任務也不會自動執行任務了
@property (nullable, copy) NSString *name ;//隊列的名字(不只是操作,隊列也可以有名字)
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue //
實例方法:
- (void)addOperation:(NSOperation *)op; //添加操作
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;//批量添加操作
- (void)addOperationWithBlock:(void (^)(void))block;//添加操作塊
- (void)cancelAllOperations; //取消所有操作,已經執行的不會被取消,這時候會設置所有子操作對象的isCanceled 為yes,自己可以在子類操作里代碼設置操作的取消,不會影響掛起的狀態
- (void)waitUntilAllOperationsAreFinished; //暫時不知道這個方法的作用
類方法
+ (nullable NSOperationQueue *)currentQueue;//返回當前線程所在的隊列
+ (NSOperationQueue *)mainQueue;//返回主隊列
一些注意點:
- 所有添加到非主隊列執行的操作都將在非主線程執行
- 自定義NSOperation:
原理:重寫start 或 main 方法,main方法由系統自動調用(注意 main方法內一定要用 @autoreleasepool)
自定義操作 通過start 開始會在當前線程執行操作,通過添加到隊列的線程會在其他線程執行
父類的 setCompletionBlock屬性設置以后會被系統自動調用,但是調用的回調block 不會是主線程也不會是當前線程而是其他線程(所以如果要用系統的回掉方法一定要手動添加在主線程回調的方法)這個block不可以傳遞參數
關于自定義操作的取消,隊列可以取消全部操作,操作也可以取消自己,但是只可以取消還沒有開始的操作,如果操作已經開始了就不能取消了,因為取消的原理就是判斷操作的屬性 isCanceled ,操作在執行時會判斷這個值,沒有開始的操作判斷操作被取消了就不會執行了,但是對于已經執行了的操作只能手動代碼改,一般情況下會在自定義操作的main方法里和合適地方(一般都是回調方法前面)判斷isCanceled的值,即使是操作已經執行完了,在這時候也可以回調失敗方法或者是直接不回調造成正在執行的操作也被取消了的假象.
關于NSOperation的子類在NSOperationQueue中執行完無法釋放的問題
感謝簡友_JD提出的問題,原文為: 你的NSOperation dealloc了么?并發數真的生效了么?
問題是這樣的: NSOperation的子類創建出來并且執行完任務以后沒有被釋放.然后問題的具體描述和解決辦法上文已經寫的很清楚啦,我就不重復一遍了.
然后說一下我對這個問題的產生的理解:
- 看完上面還是不太理解的同學可以在AFNetworking或者SDWebImage里面搜
willChangeValueForKey
看下大神是怎么用的. - NSOperation基于GCD,所以它的很多特性基本都是GCD的特性,比如控制最大并發量,應該就是封裝的信號量(因為沒有源碼所以不能百分之百確定它就是用的信號量的原理),所以它應該和GCD的特性是一樣的,在使用的時候考慮GCD的原理,可以適當避免一些錯誤.
GCD
先列一下GCD中:隊列
,同步任務
,異步任務
,線程
之間的關系,驗證代碼就不貼這里了,有興趣的同學可以挨個驗證一下:
- 串行隊列(
DISPATCH_QUEUE_SERIAL
)的同步任務:(dispatch_sync
)會在當前線程上依次執行串行隊列中的各個任務,并不會創建新的線程。 - 串行隊列(
DISPATCH_QUEUE_SERIAL
)的異步任務(dispatch_async
)會在不是主線程的另外一個線程(注意是一個
線程)上依次執行串行隊列中的各個任務。
這個是很常用的一種方式,比如:從網絡上上現在一個圖片然后處理曝光,濾鏡。。等。 - 并行隊列(
DISPATCH_QUEUE_CONCURRENT
)的同步任務(dispatch_sync
)會在當前線程上依次執行并行隊列中的各個任務,并不會創建新的線程.
這點和串行隊列的同步任務效果相同. - 并行隊列(
DISPATCH_QUEUE_CONCURRENT
)的異步任務(dispatch_async
)會給當前線程之外
的每一個任務都開啟一個線程(因為隊列中的所有任務都是異步的)分別執行每個任務.每個任務都是從頭開始的.哪個任務先結束由任務本身決定,但是系統都會給每一個任務一個單獨的線程去執行。 - 全局隊列(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
)的同步任務(dispatch_sync
)會在主線程上依次執行全局隊列中的各個任務,并不會創建新的線程. - 全局隊列(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
)的異步任務(dispatch_async
)會給當前線程之外的每一個任務都開啟一個線程(因為隊列中的所有任務都是異步的)分別執行每個任務. - 主隊列(
dispatch_get_main_queue()
)的同步任務(dispatch_sync
)會阻塞線程 - 主隊列(
dispatch_get_main_queue()
)的異步任務.(dispatch_async
)會在主線程依次執行各個任務.
總結:
- 串行隊列,并行隊列同步任務會在當前線程依次執行各個任務
- 主隊列的同步任務會阻塞線程.
- 全局隊列的同步任務會在主線程依次執行各個任務.
- 串行隊列的異步任務會在當前線程外的另外一個線程依次執行任務.并行隊列和全局隊列的異步任務會給每一個任務分配一個線程去執行,因為執行效果相同所以猜測全局隊列是并行隊列.
- 主隊列的同步任務會阻塞線程,主隊列的異步任務會在主線程上依次執行各個任務.
- 串行隊列的異步任務既可以保證效率(它會開另外一個線程)又可以實現并發.
全局隊列,主隊列,并行隊列,串行隊列區別和聯系:
- 全局隊列為系統創建,不需要自己創建,拿過來就用,用著方便
- 并行隊列和全局隊列執行效果相同
- 全局隊列沒有名稱,調試時,無法確認準確隊列
- 每一個應用程序都只有一個主線程
- iOS開發中所有UI更新工作都必須在主線程上執行,因為線程安全需要消耗性能,蘋果為了保留性能優勢所以放棄了線程安全 ,所以改UI都要在主線程上.
- 異步任務任務在主線程上是保持隊形的,也就是異步是在主線程一個線程上執行的主線程不可以執行同步任務,因為同步任務是要等到上一個線程結束后才會執行,但是主線程一直不會停止當前的任務,所以它不會去執行一個同步任務
- 無論什么隊列和什么任務,線程的創建和回收不需要程序員參與,線程的創建和回收工作是由隊列負責的
dispatch_barrier_async柵欄函數
-
dispatch_barrier_async
必須使用自定義隊列,否則執行效果和全局隊列一致,也就是說barrier任務會和其他任務一樣被當做一個普通任務去執行而不是等其他所有之前的并行任務執行完后才最后執行. - 經過大量測試不管是串行隊列或者是并行隊列的異步任務(為什么不是同步任務呢?因為同步任務都會按任務的順序執行這樣就不會有等所有線程執行完以后再執行barrier里的任務這一說了)dispatch_barrier_async會在最后一個任務執行的線程執行本任務.
-
dispatch_barrier_sync
會在主線程中執行任務。
NSLog(@">>>>>111:%@",[NSThread mainThread]);
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2:%@",[NSThread currentThread]);
});
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"dispatch-barrier:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4:%@",[NSThread currentThread]);
});
打印結果
>>>>>111:<NSThread: 0x7fc023c08bd0>{number = 1, name = main}
>>>>>111:<NSThread: 0x7fc023c08bd0>{number = 1, name = main}
dispatch-1:<NSThread: 0x7fc023f23780>{number = 2, name = (null)}
dispatch-2:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}
dispatch-barrier:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}
dispatch-4:<NSThread: 0x7fc023f23780>{number = 2, name = (null)}
dispatch-3:<NSThread: 0x7fc025b00000>{number = 3, name = (null)}
dispatch_group_t隊列組
- 隊列組只能異步執行,沒有同步執行的函數.
- dispatch_group_wait阻塞組任務一定時間:參數是一個dispatch_group_t和dispatch_time_t。dispatch_group_t是需要阻塞的組,dispatch_time_t是需要阻塞的時間.
- 線程組的用法和原理:可以給一個任務組設置一個阻塞(超時)時間,如果給一個任務組的前兩個任務設置一個超時時間那么如果這前兩個任務提前執行結束了,也就是在設定的阻塞時間內完成了任務,那么這個阻塞就會放行下面的任務去執行任務,如果超過了超時時間那么這個阻塞也會放行,讓下面的任務可以執行.阻塞時間并不能殺死任務,也就是說如果你設定了阻塞(超時)時間,阻塞時間前的任務在阻塞放行前并沒有執行完成,然后任務就被放行了,系統繼續往下執行,但是這時候阻塞時間前的任務也會執行完成如果你不手動殺死它的話.
隊列組可以手動管理也可以交給系統管理.
系統管理隊列組:
dispatch_group_async(group, queue, ^{
});
手動管理隊列組:dispatch_group_enter和dispatch_group_leave(這兩個必須成對使用)
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_group_leave(group);
});
以上的兩段代碼是相同的.
dispatch_group_notify隊列組監聽
在并行隊列里面可以同時并發執行多個任務,但是有時我們可能需要等到全部任務都執行完成以后再去執行另外一個任務,這時候就需要用到GCD的這一個特性.
先看代碼:
dispatch_queue_t queue = dispatch_queue_create("com.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@">>>>>>111:%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
});
dispatch_group_async(group, queue, ^{
NSLog(@">>>>>>222:%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_group_async(group, queue, ^{
NSLog(@">>>>>>333:%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5];
});
dispatch_group_notify(group, queue, ^{
NSLog(@">>>>>>444:%@",[NSThread currentThread]);
});
打印日志:
>>>>>>111:<NSThread: 0x7fd830414c80>{number = 2, name = (null)}
>>>>>>222:<NSThread: 0x7fd830405780>{number = 4, name = (null)}
>>>>>>333:<NSThread: 0x7fd830586c40>{number = 3, name = (null)}
>>>>>>444:<NSThread: 0x7fd830586c40>{number = 3, name = (null)}
- 可以看到最后一條打印是等到最后一個任務執行完成以后才執行最后監聽方法
- 并行隊列和全局隊列:每一個任務會分配一個線程,所有任務并行執行,最后一個任務執行完畢后會調用監測方法dispatch_group_notify,監測方法在最后一個任務執行的線程中執行
- 串行隊列:所有任務都在主線程外的另外一個線程執行,并且任務有序執行,最后一個任務執行完畢后會調用監測方法dispatch_group_notify監測方法在最后一個任務執行的線程中執行
- 主隊列:所有任務都在主線程執行,并且任務有序執行,最后一個任務執行完畢后會調用監測方法dispatch_group_notify監測方法在最后一個任務執行的線程中執行(也就是主線程)
dispatch_group_notify總結:此方法用于監聽多并發隊列,非并發無法監聽,也沒什么意義.
dispatch_semaphore_t信號量 和 dispatch_group_wait隊列組等待
信號量是用于控制并發數量的,所以只用在全局隊列和并行隊列中.
創建信號量
- 發信號dispatch_semaphore_signal(dispatch_semaphore_t)
一個參數,要發信號的信號量對象(dispatch_semaphore_t)這個函數會使傳入的信號量dsema的值加1,所以會用在線程完成后的最后面,告訴系統當前任務已經完了,你可以把這個線程騰出來給其他任務用了.
返回值(long)當返回值為0時表示當前并沒有線程等待其處理的信號量,其處理的信號量的值加1即可。當返回值不為0時,表示其當前有(一個或多個)線程等待其處理的信號量,并且該函數喚醒了一個等待的線程(當線程有優先級時,喚醒優先級最高的線程;否則隨機喚醒)
- 創建信號量(dispatch_semaphore_t)
dispatch_semaphore_create(5).
創建信號量需要一個long型參數,這個數字(必須大于等于0)標記你設置的最大線程并發量,也就是說在并發隊列里你最多允許同時創建執行5個線程去執行任務.其他任務需要在正在執行的任務完成后騰出新的線程再去執行.當然這還取決于你設置的等待時間的時長:如果設置的等待時間為永遠等待(dispatch_time_t:DISPATCH_TIME_FOREVER
)那么它會按前面說的去執行.但是如果你設置的超時時間小于你執行任務需要的時間的話.系統會創建新的線程去執行你在等待的任務.盡管這樣創建的線程會大于你設置的最大并發量.
- 設置信號量并發數的工作原理:
這個函數會使傳入的信號量dsema的值減1.這個函數的作用是這樣的,如果dsema信號量的值大于0.該函數所處線程就繼續執行下面的語句.并且將信號量的值減1.如果desema的值為0,那么這個函數就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t,不能直接傳入整形或float型數),如果等待的期間desema的值被dispatch_semaphore_signal函數加1了,且該函數(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續向下執行并將信號量減1.如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執行其后語句.
信號量等待:dispatch_semaphore_wait(dispatch_semaphore_t, dispatch_time_t)
設置等待需要兩個參數:
(a)需要等待的信號量(dispatch_semaphore_t);
(b)等待時間(dispatch_time_t)這個時間表示你可以為執行任務所等待的時間,一般都設置為永遠等待(DISPATCH_TIME_FOREVER)這樣的話如果前面的任務沒有執行完就一直等待到有任務執行完然后騰出來線程以后去執行任務.另外還可以設置一個時間(dispatch_time_t)比如說是1秒,這就意味這一秒以后如果前面的任務執行完了那么就按正常走,否則的話就新建一個另外的線程去執行等的著急的任務.
返回值(long)
如果是0就是等待成功(也就是沒有超時)否則為失敗(也就是超時了)
關于dispatch_semaphore_t:
在設置timeout時,比較有用的兩個宏:DISPATCH_TIME_NOW
和DISPATCH_TIME_FOREVER
.DISPATCH_TIME_NOW
表示當前.DISPATCH_TIME_FOREVER
表示遙遠的未來.一般可以直接設置timeout為這兩個宏其中的一個,或者自己創建一個dispatch_time_t類型的變量.
創建dispatch_time_t類型的變量有兩種方法,dispatch_time和dispatch_walltime.利用創建dispatch_time創建dispatch_time_t類型變量的時候一般也會用到這兩個變量.
dispatch_time的聲明如下:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);其參數when需傳入一個dispatch_time_t類型的變量,和一個delta值.表示when加delta時間就是timeout的時間.例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW,110001000*1000);表示當前時間向后延時一秒為timeout的時間.
代碼示例:
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i++){
//dispatch_time_t wait = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_time_t wait = DISPATCH_TIME_FOREVER;
dispatch_semaphore_wait(semaphore, wait);
dispatch_group_async(group, queue, ^{
NSLog(@"----------------:%i :%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_notify(group, queue, ^{
NSLog(@"xxxxxxxxxxxxxx");
});
打印日志:
2016-11-15 23:08:25.894 多線程總結[86647:6419764] ----------------:0 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:25.894 多線程總結[86647:6419757] ----------------:1 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:25.894 多線程總結[86647:6419768] ----------------:2 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:26.898 多線程總結[86647:6419764] ----------------:5 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:26.898 多線程總結[86647:6419768] ----------------:3 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:26.898 多線程總結[86647:6419757] ----------------:4 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:27.904 多線程總結[86647:6419757] ----------------:7 :<NSThread: 0x7fc011f0ac80>{number = 3, name = (null)}
2016-11-15 23:08:27.904 多線程總結[86647:6419764] ----------------:6 :<NSThread: 0x7fc011d1ac90>{number = 2, name = (null)}
2016-11-15 23:08:27.904 多線程總結[86647:6419768] ----------------:8 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:28.908 多線程總結[86647:6419768] ----------------:9 :<NSThread: 0x7fc011c0f880>{number = 4, name = (null)}
2016-11-15 23:08:29.913 多線程總結[86647:6419768] xxxxxxxxxxxxxx
代碼分析:
首先這里面創建了一個組,這個組放在這里只是告訴你可以這么用,它不會影響信號量的功能.
這里創建了一個并發為3的信號量然而for循環是10個任務,那么理論上10個任務會創建十個線程去執行,但是你信號量為3所以只能創建3個線程去執行前面10個任務,然后等待前3個任務執行完成了騰出來新的線程再去執行等待執行的.所以下面的線程 number 最大是4,當然這前提是你設置的超時時間大于任務執行完的時間.如果設置超時時間是0.1秒的話,任務等超時了還是會開啟另外的線程去執行任務,這樣就達不到控制并發量的要求了.
關于信號量舉個小栗子幫助大家理解下:
一般可以用停車來比喻:
停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待。信號量的值就相當于剩余車位的數目,dispatch_semaphore_wait函數就相當于來了一輛車,dispatch_semaphore_signal就相當于走了一輛車。停車位的剩余數目在初始化的時候就已經指明了(dispatch_semaphore_create(long value)),調用一次dispatch_semaphore_signal,剩余的車位就增加一個;調用一次dispatch_semaphore_wait剩余車位就減少一個;當剩余車位為0時,再來車(即調用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己設定了一段等待時間,這段時間內等不到停車位就走了,如果等到了就開進去停車。而有些車主就像把車停在這,所以就一直等下去。
NSOperation與GCD的對比
- GCD
- 將任務(block)添加到隊列(串行/并發/主隊列),并且指定任務執行的函數(同步/異步)
- GCD是底層的C語言構成的API
- iOS 4.0 推出的,針對多核處理器的并發技術
- 在隊列中執行的是由 block 構成的任務,這是一個輕量級的數據結構
- 要停止已經加入 queue 的 block 需要寫復雜的代碼
- 需要通過 Barrier 或者同步任務設置任務之間的依賴關系
- 只能設置隊列的優先級
- 高級功能:
- 一次性 once
- 延遲操作 after
- 調度組
- NSOperation
- 核心概念:把操作(異步)添加到隊列(全局的并發隊列)
- OC 框架,更加面向對象,是對 GCD 的封裝
- iOS 2.0 推出的,蘋果推出 GCD 之后,對 NSOperation 的底層全部重寫
- Operation作為一個對象,為我們提供了更多的選擇
- 可以隨時取消已經設定要準備執行的任務,已經執行的除外
- 可以跨隊列設置操作的依賴關系
- 可以設置隊列中每一個操作的優先級
- 高級功能:
- 最大操作并發數(GCD不好做)
- 繼續/暫停/全部取消
- 跨隊列設置操作的依賴關系
iOS的常用多線程使用方式基本都在這里了.但是這里沒有說線程安全,原理,線程鎖,runloop等和線程相關的東西的.先這樣,下次再詳細說說更深點的東西.
上面的內容是很久以前筆記記下的東西,現在沒重新做驗證,如有錯誤,望請指正,多謝多謝.
在篇尾
程序員不需要打賞,只希望自己的項目能幫助更多人,請支持我的git開源框架:TFEasyCoder
原創內容,轉載請注明出處:
http://www.lxweimin.com/p/ac11fe7ef78c
如果我的文章對您有幫助請不吝喜歡和關注~~