今天沒什麼事做就對iOS的多線程做一次總結,純屬個人看法,初學者可以參考下。對於多線程我想無論是面試還是實際開發大家都不會陌生;
嚴格意義來講iOS多線程算是4種:
- PThreads
- NSThread
- GCD
- NSOPeration/NSOperationQueue;
PThreads
- 調用的都是C函數和語法,對於習慣OC或者Swift開發的我們來說,很蛋疼;
- 線程的生命週期需要我們手動管理。
- (void)Pthreads {
pthread_t thread;
//創建一個線程並執行 start是一個函數指針
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
//這裏執行線程操作
NSLog(@"%@",[NSThread currentThread]);
return NULL;
}
有興趣的同學可以看看。NSThread
- (void)NSThread {
//先創建後執行
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
[thread start];
//創建並執行
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];
//perform裏唯一一個多線程方法 如果線程方法要刷新UI可以使用
//- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
[self performSelectorInBackground:@selector(threadRun) withObject:nil];
}
- (void)threadRun {
NSLog(@"threadRun:%@",[NSThread currentThread]);
}
GCD
//1.初始化/獲取系統隊列
//獲取主隊列 ()一般只負責刷新UI,耗時任務都交給其他線程
dispatch_queue_t queue = dispatch_get_main_queue();
//創建自己的隊列
//串行隊列
dispatch_queue_t queue1 = dispatch_queue_create("隊列標識符", nil);
dispatch_queue_t queue2 = dispatch_queue_create("隊列標識符", DISPATCH_QUEUE_SERIAL);
//並行隊列
dispatch_queue_t queue3 = dispatch_queue_create("隊列標識符", DISPATCH_QUEUE_CONCURRENT);
//獲取全局並行隊列 只要是並行任務一般都加入到這個隊列。這是系統提供的一個併發隊列。
dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.任務的創建和執行方法(同步和異步)
//同步任務
dispatch_sync(queue3, ^{
NSLog(@"GCD-同步%@",[NSThread currentThread]);
sleep(5);
NSLog(@"GCD-同步—後%@",[NSThread currentThread]);
});
//異步任務
dispatch_async(queue3, ^{
NSLog(@"GCD-異步%@",[NSThread currentThread]);
});
從上面的代碼可以看出隊列還主隊列(系統隊列)還有自定義隊列(多線程),自定義隊列創建的時候兩個參數第一個是標識符可以爲空;第二個爲空或者DISPATCH_QUEUE_SERIAL爲串行隊列,DISPATCH_QUEUE_CONCURRENT爲並行隊列。還有一個全局並行隊列一般的併發任務都會加入這個隊列,其實無論是並行隊列還是串行隊列都是以先進先出原則,區別在於並行隊列是取一個開一個線程執行,在取下一個,中間取的時間很快;串行隊列是取出一個執行完成以後再取出下一個,一個一個的執行。需要注意的是GCD會根據系統資源限制並行數量。
//3.隊列組 隊列組可以將很多隊列添加到一個組裏,這樣做的好處是,當這個組裏所有的任務都執行完了,隊列組會通過一個方法通知我們。
//3.1創建隊列組
dispatch_group_t group = dispatch_group_create();
//3.2創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.3多次使用隊列組的方法執行任務,只有異步方法
//執行3此循環
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"group-01:%@",[NSThread currentThread]);
}
});
//主隊列執行8此循環
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 8; i++) {
NSLog(@"group-02:%@",[NSThread currentThread]);
}
});
//執行5次循環
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"group-03:%@",[NSThread currentThread]);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成-04:%@",[NSThread currentThread]);
});
NSOperation/NSOperationQueue
NSOperation 是蘋果公司對 GCD的封裝,完全面向對象,所以使用起來更好理解。大家可以看到 NSOperation和 NSOperationQueue分別對應 GCD的任務和隊列
NSOperation只是一個抽象類,所以不能封裝任務。但它有 2個子類用於封裝任務。
分別是:NSInvocationOperation和 NSBlockOperation。
創建一個 Operation後,需要調用 start方法來啓動任務,它會默認在當前隊列同步執行。
當然你也可以在中途取消一個任務,只需要調用其 cancel方法即可
//1.創建
//NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
//開始執行
[operation start];
//NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation:%@",[NSThread currentThread]);
}];
/*
NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 添加多個執行 Block。這樣 Operation 中的任務會併發執行,它會在主線程和其它的多個線程 執行這些任務
*/
for (int i = 0; i < 5; i++) {
[blockOperation addExecutionBlock:^{
NSLog(@"blockOperation-第%d次:%@",i,[NSThread currentThread]);
}];
}
[blockOperation start];
/*
看過上面的內容就知道,我們可以調用一個 NSOperation 對象的 start() 方法來啓動這個任務,但是這樣做他們默認是 同步執行 的。就算是 addExecutionBlock 方法,也會在 當前線程和其他線程 中執行,也就是說還是會佔用當前線程。這是就要用到隊列 NSOperationQueue 了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調用任務的 start() 方法
*/
//NSOperationQueue 獲取主隊列(串行)
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//創建其他隊列
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//這裏可以添加多個operation
[queue1 addOperation:operation1];
NSOperation 有一個非常實用的功能,那就是添加依賴。比如有 3個任務:A:從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了:
//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片:%@",[NSThread currentThread]);
}];
//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印:%@",[NSThread currentThread]);
}];
//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片:%@",[NSThread currentThread]);
}];
//4.設置依賴 注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
[operation2 addDependency:operation1]; //任務二依賴任務一
[operation3 addDependency:operation2]; //任務三依賴任務二
//5.創建隊列並加入
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:[NSArray arrayWithObjects:operation1,operation2,operation3, nil] waitUntilFinished:NO];
以上只是一些基本使用方法,想了解更多可以去網上找或者去看官方文檔;
使用多線程我們就需要考慮數據同步的問題
其他
互斥鎖:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。
@synchronized(self) {
//需要執行的代碼塊
}
我們也可以在隊列的結尾加一個串行隊列,來達到數據同步的效果方法有很多。