iOS開發-多執行緒程式設計

机械心發表於2024-07-22

OC中常用的多執行緒程式設計技術:

1. NSThread

NSThread是Objective-C中最基本的執行緒抽象,它允許程式設計師直接管理執行緒的生命週期。

NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];
[myThread start];

使用NSThread時,需要自己管理執行緒的生命週期,包括建立、啟動和銷燬執行緒。這種方法給了開發者很大的控制權,但也增加了複雜性,因為需要手動處理執行緒同步和執行緒安全問題。

2. Grand Central Dispatch (GCD)

GCD是一個強大的基於C語言的API,它提供了一個併發執行任務的低階別方式。GCD使用任務佇列和執行緒池來最佳化執行緒的使用。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 非同步執行的任務
});

GCD是推薦的多執行緒程式設計方法之一,因為它的效能很好,而且簡化了併發程式設計的複雜性(下一節詳細介紹)。

3. Operation Queues

NSOperationNSOperationQueue提供了一個物件導向的方式來執行併發操作。NSOperation是一個抽象類,可以透過繼承它來定義具體的操作。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod) object:nil];
[queue addOperation:operation];

NSOperationQueue可以管理多個NSOperation物件,它支援設定最大併發運算元和依賴關係。NSOperation比GCD更高階,它支援取消操作、設定操作依賴和觀察操作狀態。

4. Perform Selector Methods

Objective-C提供了performSelector:onThread:等方法來在指定的執行緒上執行方法。

[self performSelector:@selector(myTaskMethod) onThread:myThread withObject:nil waitUntilDone:NO];

這些方法簡單易用,但它們不提供GCD和NSOperation的強大功能。

5. POSIX Threads (pthreads)

POSIX執行緒是一套跨平臺的執行緒相關的API,Objective-C可以直接使用這些API,因為它是C語言的超集。

#include <pthread.h>

void *myThreadFunction(void *context) {
    // 執行緒執行的程式碼
    return NULL;
}

pthread_t thread;
pthread_create(&thread, NULL, myThreadFunction, NULL);

pthreads提供了很大的控制力,但它是一個低階別的API,通常不推薦在OC中使用,除非需要與C庫互動或有特殊的執行緒管理需求。

GCD的詳細介紹

Grand Central Dispatch (GCD) 是 Apple 開發的一個強大的多核程式設計解決方案,它提供了一種簡單且高效的方式來管理併發任務。GCD 使用任務(blocks of code)和佇列(queues)的概念來執行工作。它是基於 C 語言實現的,可以在 Objective-C 和 Swift 中使用。

核心概念

任務

任務是指要執行的工作,通常是以 block 的形式提供。在 Objective-C 中,一個任務可以是一個 block 或者一個函式。

佇列

佇列是一種特殊的資料結構,用於按順序儲存任務。GCD 提供了兩種型別的佇列:

  • 序列佇列(Serial Queue):一次只執行一個任務。佇列中的任務按照新增的順序依次執行。在內部實現了一種機制,確保佇列中的任務一次只能執行一個,並且按照它們被新增到佇列中的順序來執行。這是透過佇列管理和任務排程來實現的。

當將一個任務提交到序列佇列時,這裡是大致的工作流程:

  1. 任務排隊:任務被新增到佇列的末尾。如果佇列是空的,它會成為佇列中的第一個任務。

  2. 任務執行:佇列中的第一個任務(隊頭)被取出來執行。在這個任務執行期間,佇列不會執行或者開始執行任何其他任務。

  3. 任務完成:一旦當前執行的任務完成,它會從佇列中移除。

  4. 下一個任務:佇列中的下一個任務(現在是隊頭)開始執行。

  5. 重複過程:這個過程會一直重複,直到佇列中的所有任務都被執行完畢。

序列佇列的關鍵特性是互斥執行,這意味著在任何給定時間點,佇列中只有一個任務在執行。這種互斥是由GCD的底層排程機制保證的,它確保了即使在多核處理器上,序列佇列上的任務也不會並行執行。

這種執行方式使得序列佇列成為了同步執行任務的理想選擇,特別是需要按順序執行一系列任務,而這些任務又不能同時執行時(例如,當任務需要按特定順序訪問或修改共享資源時)。因此理論上一個執行緒就足夠了。這是序列佇列如何保證任務順序執行和互斥的關鍵。當一個任務在序列佇列中開始執行時,它會持續執行直到完成,然後佇列才會執行下一個任務。這個過程不需要同時有多個執行緒參與,因為不會有並行執行的情況。然而,實際上,由於GCD的工作原理,它可能會在內部使用多個執行緒來管理多個序列佇列。GCD使用執行緒池來最佳化執行緒的使用,這意味著它會根據需要動態地為佇列分配和回收執行緒。但對於任何單一的序列佇列來說,你可以認為它在任何時候都只在一個執行緒上執行任務。這種設計使得序列佇列成為管理共享資源和避免併發問題的理想工具,因為它簡化了同步和執行緒安全的需求。同時,它也減少了上下文切換的開銷,因為任務是在單個執行緒上連續執行的。

  • 並行佇列(Concurrent Queue):可以同時執行多個任務。任務可以併發執行,但完成的順序可能會不同。

系統佇列

GCD 提供了幾種不同型別的系統佇列:

  • 主佇列(Main Queue):序列佇列,用於在主執行緒上執行任務,通常用於更新 UI。
  • 全域性佇列(Global Queues):並行佇列,有四個不同優先順序的全域性佇列:高、預設、低和後臺。

Grand Central Dispatch (GCD) 提供了幾種不同型別的系統佇列,這些佇列是預先建立好的,可以直接使用。它們分為兩大類:主佇列(Main Queue)和全域性佇列(Global Queues)。

主佇列(Main Queue)
  • 主佇列是一個特殊的序列佇列,它在應用程式的主執行緒上執行任務。因為主執行緒通常用於更新UI,所以所有的UI更新都應該在主佇列上執行,以確保UI的平滑和響應性。
  • 使用dispatch_get_main_queue()函式可以獲取主佇列。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
    // 在主執行緒上更新UI
});
全域性佇列(Global Queues)
  • 全域性佇列是並行佇列,它們在後臺執行任務,不會阻塞主執行緒。全域性佇列有四個不同的優先順序:高(high)、預設(default)、低(low)和後臺(background)。這些優先順序對應於系統為任務分配的相對重要性。
  • 使用dispatch_get_global_queue()函式可以獲取全域性佇列,需要指定優先順序和一個保留用的標誌位(目前應該傳遞0)。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 在後臺執行耗時任務
});

全域性佇列的優先順序:

  • 高優先順序DISPATCH_QUEUE_PRIORITY_HIGH):用於需要立即執行的任務,但不應該阻塞主執行緒。
  • 預設優先順序DISPATCH_QUEUE_PRIORITY_DEFAULT):用於大多數任務,如果沒有特殊的優先順序要求,應該使用這個優先順序。
  • 低優先順序DISPATCH_QUEUE_PRIORITY_LOW):用於不急迫的任務,可以等待其他更重要的任務完成後再執行。
  • 後臺優先順序DISPATCH_QUEUE_PRIORITY_BACKGROUND):用於那些使用者不太可能立即注意到的任務,如預取資料、維護或清理工作。
    注意以下事項:
  • 儘管全域性佇列是並行佇列,但是任務的啟動順序仍然是按照它們被新增到佇列的順序。
  • 全域性佇列不保證任務完成的順序,任務可以併發執行。
  • 主佇列保證任務按照新增的順序一個接一個地執行。
  • 在主佇列上同步執行任務會導致死鎖,因為主佇列等待同步任務完成,而同步任務又在等待主佇列可用,從而形成了相互等待的情況。

自定義佇列

除了系統佇列,GCD還允許建立自定義佇列。自定義佇列可以是序列的也可以是並行的。

  • 建立序列佇列:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.mySerialQueue", DISPATCH_QUEUE_SERIAL);
  • 建立並行佇列:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

使用 GCD

非同步執行

使用 dispatch_async 函式可以非同步地將任務提交到佇列中。這意味著它不會等待任務完成,而是立即返回。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
    // 執行耗時的任務
});

同步執行

使用 dispatch_sync 函式可以同步地將任務提交到佇列中。這會阻塞當前執行緒,直到任務執行完成。

dispatch_sync(queue, ^{
    // 執行任務
});

延遲執行

使用 dispatch_after 函式可以在指定的時間後非同步執行任務。

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
    // 2秒後執行的任務
});

一次性執行

使用 dispatch_once 函式可以確保程式碼塊只被執行一次,常用於建立單例。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行一次的程式碼
});

佇列組

dispatch_group 允許多個任務作為一個組來提交,並在組中的所有任務完成時得到通知。

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    // 任務1
});
dispatch_group_async(group, queue, ^{
    // 任務2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 所有任務完成後執行
});

訊號量

dispatch_semaphore 用於控制訪問資源的執行緒數量,可以用來實現執行緒同步。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    // 訪問受限資源
    dispatch_semaphore_signal(semaphore);
});

注意事項

  • 不要在序列佇列上同步地執行任務,這可能會導致死鎖。
  • 儘量避免在主佇列上同步執行耗時任務,這會阻塞 UI 更新。
  • 使用 GCD 時,要注意記憶體管理,特別是在 block 中捕獲外部變數時。

相關文章