Objective-C基礎筆記整理(三)多執行緒篇
多執行緒
1、基礎概念
- 程序:在系統中正在執行的一個應用程式,例如開啟常用的一個軟體,系統會啟動一個程序,每個執行緒之間是相互獨立的。
- 執行緒:一個程序要想執行任務,必須至少有一條執行緒(主執行緒),執行緒是程序執行任務的最小單位。
- 執行緒的序列:一個執行緒只能執行一個任務,如果要執行多個任務,那麼只能一個一個地按順序執行這些任務。
- 多執行緒:一個程序可以開啟多條執行緒,每條執行緒可以同時執行不同的任務。
多執行緒的原理
1、CPU只能處理一條執行緒,只能在一條執行緒中工作。 2、多執行緒併發,其實是CPU快速的在多條執行緒之間的排程。 3、如果開啟的執行緒過多會消耗大量的CPU資源,降低執行效率。 4、一般開啟 3 ~ 5 個執行緒。
2、多執行緒實現方案
- pthread:通用的多執行緒API,適用於Unix、Linux、Windows等系統,跨平臺、可移植,使用難度大,C語言,執行緒生命週期由程式設計師管理。
- NSTread:面向物件,簡單易用,可直接操作執行緒物件,OC語言,執行緒生命週期由程式設計師管理。
- GCD:開發中常用的一種方案,替代NSTread等執行緒技術,充分利用裝置的多核,C語言,執行緒生命週期自動管理。
- NSOperation:底層是GCD,比GCD多了一些更簡單實用的功能,使用更加面向物件,OC語言,執行緒生命週期自動管理。
2.1、pthread
建立執行緒物件
pthread_t thread;
建立執行緒
/*
第一個引數 執行緒物件
第二個引數 執行緒屬性
第三個引數 要執行的任務
第四個引數 函式的引數
*/
pthread_create(&thread, NULL, run ,NULL);
設定子執行緒的狀態設定為detach,該執行緒執行結束後會自動釋放所有資源。
pthread_detach()
執行方法
void *run(void *param)
{
for (NSInteger i = 0; i < 10000 ; i++) {
NSLog(@"%zd", i);
}
return NULL;
}
2.2、NSTread
建立執行緒
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
設定執行緒名字
thread.name = @"running";
qualityOfService 設定優先順序
/*
NSQualityOfServiceUserInteractive: 最高:主要用於與UI互動的操作,各種事件處理以及繪製圖像等。
NSQualityOfServiceUserInitiated: 次高:執行一些明確需要立即返回結果的任務。
NSQualityOfServiceDefault: 預設:預設的優先順序,介於次高階和普通級之間。
NSQualityOfServiceUtility: 普通:用於執行不許要立即返回結果、耗時的操作,下載或者一些媒體操作等。
NSQualityOfServiceBackground: 後臺:後臺執行一些使用者不需要知道的操作,它將以最有效的方式執行。例如一些預處理的操作,備份或者同步資料等等。
*/
thread.qualityOfService = NSQualityOfServiceUserInitiated;
開啟執行緒
[thread start];
其它常用方法
//判斷是否是多執行緒
+ (BOOL)isMultiThreaded;
//執行緒休眠
+ (void)sleepUntilDate:(NSDate *)date;
//執行緒休眠時間
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出當前執行緒
+ (void)exit;
//獲取當前執行緒優先順序
+ (double)threadPriority;
// 設定執行緒優先順序 預設為0.5 取值範圍為0.0 - 1.0,1.0優先順序最高
+ (BOOL)setThreadPriority:(double)p;
執行緒通訊
1、在1個程序中,多個執行緒之間需要經常進行通訊。
2、例如我們在子執行緒獲取資料後,要回到主執行緒重新整理介面。
3、1個執行緒傳遞資料給另1個執行緒。
4、在1個執行緒中執行完特定任務後,轉到另1個執行緒繼續執行任務。
回到主執行緒或者轉到另一個執行緒的方法
// 返回主執行緒
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定執行緒
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
執行緒狀態描述
1、建立執行緒物件後,執行緒處於新建New的狀態。
2、呼叫start 開啟執行緒後,會將執行緒加入到可排程執行緒池中,此時進入就緒Runnable狀態。
3、當CPU排程到當前執行緒的時候,這時就會處於執行Running的狀態。
4、當CPU排程其他執行緒的時候,當前執行緒又回到就緒狀態。
5、當我們對當前執行緒呼叫了sleep或者等待同步鎖的時候,此時又會進入執行緒阻塞Blocked狀態。
加入到可排程執行緒池中。
6、當任務執行完畢或強制退出後,進入到死亡Dead狀態,此時執行緒物件被釋放。
借一張圖表示一下:
執行緒安全
1、問題:多條執行緒搶奪同一塊資源,可能會導致資料錯亂和資料安全的問題。
2、解決:互斥鎖,加鎖可以使執行緒同步(多條執行緒在同一條線上按順序的執行任務)。
3、優點:有效防止因多執行緒搶奪資源造成的資料安全問題。
4、缺點:需要消耗大量的CPU資源。
//self : 鎖物件,必須全域性唯一
@synchronized (self) {
//程式碼片段,需要做的操作
}
2.3、GCD
GCD優點
- 自動管理執行緒生命週期(建立執行緒–>排程任務–>銷燬執行緒)。
- 只需要告訴GCD想要執行什麼任務,不需要編寫任何執行緒管理程式碼。
核心概念:任務和佇列
- 任務:執行什麼操作。
佇列:存放任務。
1、將任務新增到佇列中,GCD會自動將佇列中的任務取出,放到對應的執行緒中執行。 2、任務的取出遵循佇列的FIFO原則:先進先出,後進後出。
任務
(1)、同步任務:只能在當前執行緒執行任務,不具備開啟新執行緒的能力.
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
(2)、非同步任務:在新的執行緒中執行任務,具備開啟執行緒的能力.
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
佇列
(1)、併發佇列:可以讓多個任務同時執行,併發功能只有在非同步函式下才有效.
//1.建立併發佇列
/**
第一個引數:c語言字串,標籤
第二個引數:佇列的型別
DISPATCH_QUEUE_CONCURRENT 併發
DISPATCH_QUEUE_SERIAL 序列
*/
dispatch_queue_t creatCurrrentQueue = dispatch_queue_create("creatCurrrentQueue", DISPATCH_QUEUE_CONCURRENT);
//2.獲取全域性的併發佇列
/**
第一個引數:優先順序 選擇預設的優先順序
第二個引數:留給以後用的 暫時傳0
*/
dispatch_queue_t getGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
(2)、序列佇列:讓任務一個接一個執行.
//1.建立序列佇列
dispatch_queue_t creatQueue = dispatch_queue_create("creatQueue", DISPATCH_QUEUE_SERIAL);
//2.獲取全域性的序列佇列主佇列 主佇列是GCD自帶的一種特殊的序列佇列,放在主佇列中的任務,都會放到主執行緒中執行
dispatch_queue_t getQueue = dispatch_get_main_queue();
常用組合方式
// 非同步函式 + 獲取全域性併發佇列 --- 例如獲取資料
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 非同步函式 + 獲取全域性序列佇列(主佇列)--- 例如 回到主執行緒 重新整理UI介面
dispatch_async(dispatch_get_main_queue(), ^{
});
});
同步函式 + 主佇列:死鎖
//1.獲得主佇列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.同步函式
dispatch_sync(queue, ^{
NSLog(@"---download1---%@",[NSThread currentThread]);
});
1、給主執行緒中新增任務,同步函式要求立刻馬上執行任務,而主佇列安排主執行緒來執行任務,
但當前主執行緒在等待方法執行完畢, 因此就會相互等待而發生死鎖。
2、如果此方法在子執行緒中呼叫,則不會形成死鎖。
其它GCD使用方式
(1)、延遲執行
/*
第一個引數:延遲時間
第二個引數:要執行的程式碼
如果想讓延遲的程式碼在子執行緒中執行,也可以更改在哪個佇列中執行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
*/
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
//執行內容
});
(2)、只執行一次,例如單例
//整個程式執行過程中只會執行一次
//onceToken用來記錄該部分的程式碼是否被執行過
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
(3)、佇列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的非同步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的非同步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的非同步操作都執行完畢後,回到主執行緒...
});
(4)、快速迭代:開啟多條執行緒,併發執行,相比於for迴圈在耗時操作中極大的提高效率和速度
/*
第一個引數:迭代的次數
第二個引數:在哪個佇列中執行
第三個引數:block要執行的任務
*/
dispatch_apply(10, queue, ^(size_t index) {
});
2.4、NSOperation
NSOperation是蘋果對GCD的封裝,完全面向物件,不用考慮執行緒的生命週期、同步、加鎖等問題。
NSOperation和NSOperationQueue分別對應GCD的任務和佇列。
NSOperation和NSOperationQueue實現多執行緒的具體步驟:
1、將需要執行的操作封裝到一個NSOperation物件中。
2、將NSOperation物件新增到NSOperationQueue中。
3、系統會自動將NSOperationQueue中的NSOperation取出來,將其封裝的操作放到一條新執行緒中執行。
NSOperation
NSOperation是個抽象類,並不具備封裝操作的能力,必須使用它的子類,它的子類有三種:
(1)、NSInvocationOperation
/*
第一個引數:目標物件
第二個引數:選擇器,要呼叫的方法
第三個引數:方法要傳遞的引數
*/
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
//啟動操作
[op start];
(2)、NSBlockOperation(常用)
//1.封裝操作
NSBlockOperation *blockOperation= [NSBlockOperation blockOperationWithBlock:^{
//要執行的操作,在主執行緒中執行
}];
//2.追加操作,追加的操作在子執行緒中執行,可以追加多條操作
[blockOperation addExecutionBlock:^{
//追加的操作
}];
//啟動操作
[blockOperation start];
(3)、自定義子類繼承NSOperation,實現內部相應的方法
-(void)main
{
// 要執行的操作
}
NSOperationQueue
佇列:
(1)、主佇列:通過mainQueue獲得,凡是放到主佇列中的任務都將在主執行緒執行
(2)、非主佇列:直接alloc init出來的佇列。非主佇列同時具備了併發和序列的功能,通過設定最大併發數屬性來控制任務是併發執行還是序列執行
(3)、NSOperation可以呼叫start方法來執行任務,但預設是同步執行的,如果將NSOperation新增到NSOperationQueue(操作佇列)中,系統會自動非同步執行NSOperation中的操作
新增操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;
NSOperation和NSOperationQueue結合使用建立多執行緒
// 1. 建立非主佇列 同時具備併發和序列的功能,預設是併發佇列
NSOperationQueue *queue =[[NSOperationQueue alloc]init];
// 2. 封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
//操作
}];
// 3. 將封裝操作加入主佇列
// 也可以不獲取封裝操作物件 直接新增操作到佇列中
//[queue addOperationWithBlock:^{
// 操作
//}];
[queue addOperation:op1];
NSOperation和NSOperationQueue的重要屬性和方法
NSOperation的依賴
// 操作one依賴two,即one必須等two執行完畢之後才會執行
[one addDependency:two];
// 移除依賴
[one removeDependency:two];
NSOperation操作監聽
op1.completionBlock = ^{
NSLog(@"op1已經完成了---%@",[NSThread currentThread]);
};
NSOperation執行緒之間的通訊方法
// 回到主執行緒重新整理UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//重新整理操作
}];