主頁 >  其他 > OC底層探索(十九) 多執行緒

OC底層探索(十九) 多執行緒

2020-11-03 06:02:48 其他

OC底層文章匯總

1.行程與執行緒

1.1 行程

  • 行程是指在系統中正在運行的一個應用程式
  • 每個行程之間是獨立的,每個行程均運行在其專用的且受保護的記憶體空間內
  • 目前在iOS中,一個行程就是一個app,

1.2 執行緒

  • 執行緒行程基本執行單元,一個行程的所有任務都在執行緒執行.
  • 行程要想執行任務,必須得有執行緒,行程至少要有一條執行緒
  • 程式啟動默認開啟一條執行緒,這條執行緒被稱為主執行緒UI 執行緒

1.3 行程與執行緒的關系

  • 地址空間:同一行程的執行緒共享行程地址空間,而行程之間則是獨立的地址空間,
  • 資源擁有:同一行程內的執行緒共享行程資源記憶體I/Ocpu等,但是行程之間的資源是獨立的,

區別:

1: 一個行程崩潰后,在保護模式下不會對其他行程產生影響,但是一個執行緒崩潰整個進 程都死掉,所以多行程要比多執行緒健壯,

2: 行程切換時,消耗的資源大,效率高,所以涉及到頻繁的切換時,使用執行緒要好于進 程,同樣如果要求同時進行并且又要共享某些變數的并發操作,只能用執行緒不能用行程

3: 執行程序:每個獨立的行程有一個程式運行的入口、順序執行序列和程式入口,但是 執行緒不能獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制,

4: 執行緒是處理器調度的基本單位,但是行程不是, 5: 執行緒沒有地址空間,執行緒包含在行程地址空間中

2 多執行緒

2.1 概念

在一個行程中開啟多條執行緒,每條執行緒可以并行執行不同的任務,

  • 多執行緒的原理
    • 同一時間,CPU只能處理一條執行緒,只有一條執行緒在作業
    • 多執行緒并發,其實是CPU快速的在多條執行緒之間切換
    • 如果多執行緒切換速度特別快,就造成了多執行緒并發執行的假象

注意 : 如果執行緒非常多,CPU會在N多執行緒之間切換,CPU消耗就會特別大,每條執行緒被呼叫額頻率就會被降低,所有要適當使用多執行緒

  • 多執行緒的優缺點
  • 優點
  • 能適當提高程式的執行效率
  • 能適當提高資源的利用率(CPU,記憶體)
  • 執行緒上的任務執行完成后,執行緒會自動銷毀
  • 缺點
  • 開啟執行緒需要占用一定的記憶體空間(默認情況下,每一個執行緒都占 512 KB)
  • 如果開啟大量的執行緒,會占用大量的記憶體空間,降低程式的性能
  • 執行緒越多,CPU 在呼叫執行緒上的開銷就越大
  • 程式設計更加復雜,比執行緒間的通信、多執行緒的資料共享

2.2 多執行緒的方式

2.2.1多執行緒方案

在這里插入圖片描述

2.2.2 執行緒的生命周期

在這里插入圖片描述

  • 新建:實體化執行緒物件

  • 就緒:向執行緒物件發送start訊息,執行緒物件被加入可調度執行緒池等待CPU調度,

  • 運行:CPU負責調度可調度執行緒池中執行緒的執行,執行緒執行完成之前,狀態可能會在就緒和運行之間來回切換,就緒和運行之間的狀態變化由CPU負責,程式員不能干預,

  • 阻塞:當滿足某個預定條件時,可以使用休眠或鎖,阻塞執行緒執行,sleepForTimeInterval(休眠指定時長),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖),

  • 死亡:正常死亡,執行緒執行完畢,非正常死亡,當滿足某個條件后,在執行緒內部中止執行/在主執行緒中止執行緒物件

    • 還有執行緒的exit和cancel
    • [NSThread exit]:一旦強行終止執行緒,后續的所有代碼都不會被執行,
    • [thread cancel]取消:并不會直接取消執行緒,只是給執行緒物件添加 isCancelled 標記,

2.3 NSThread、GCD、NSOperation

2.3.1 NSThread

2.3.1.1 創建執行緒

  • init方式,需要手動啟動
/** 方法一,需要start */
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
// 執行緒加入執行緒池等待CPU調度,時間很快,幾乎是立刻執行
[thread1 start];
  • detachNewThreadSelector創建好之后自動啟動
/** 方法二,創建好之后自動啟動 */
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
  • performSelectorInBackground創建好之后也是直接啟動
/** 方法三,隱式創建,直接啟動 */
[self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];

2.3.1.2 NSThread的類方法

  • 回傳當前執行緒
// 當前執行緒
[NSThread currentThread];
NSLog(@"%@",[NSThread currentThread]);

// 如果number=1,則表示在主執行緒,否則是子執行緒
列印結果:{number = 1, name = main}
  • 阻塞休眠
//休眠多久
[NSThread sleepForTimeInterval:2];
//休眠到指定時間
[NSThread sleepUntilDate:[NSDate date]];
  • 其他的方法
//退出執行緒
[NSThread exit];
//判斷當前執行緒是否為主執行緒
[NSThread isMainThread];
//判斷當前執行緒是否是多執行緒
[NSThread isMultiThreaded];
//主執行緒的物件
NSThread *mainThread = [NSThread mainThread];
  • 其他屬性
 //執行緒名
 thread.name;
//執行緒是否在執行
thread.isExecuting;
//執行緒是否被取消
thread.isCancelled;
//執行緒是否完成
thread.isFinished;
//是否是主執行緒
thread.isMainThread;
//執行緒的優先級,取值范圍0.0到1.0,默認優先級0.5,1.0表示最高優先級,優先級高,CPU調度的頻率高
 thread.threadPriority;

  • 執行緒之間的通信
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;


2.3.2 GCD

2.3.2.1 概念

GCD全稱Grand Central Dispatch,純C語言,提供了非常大的函式,

任務(block) :任務就是將要在執行緒中執行的代碼,將這段代碼用block封裝好,然后將這個任務添加到指定的執行方式(同步執行和異步執行),等待CPU從佇列中取出任務放到對應的執行緒中執行,

同步(sync) :一個接著一個,前一個沒有執行完,后面不能執行,不開執行緒,

異步(async):開啟多個新執行緒,任務同一時間可以一起執行,異步是多執行緒的代名詞

佇列:裝載執行緒任務的隊形結構,(系統以先進先出的方式調度佇列中的任務執行),在GCD中有兩種佇列:串行佇列和并發佇列,

并發佇列:執行緒可以同時一起進行執行,實際上是CPU在多條執行緒之間快速的切換,(并發功能只有在異步(dispatch_async)函式下才有效)

串行佇列:執行緒只能依次有序的執行,

GCD總結:將任務(要在執行緒中執行的操作block)添加到佇列(自己創建或使用全域并發佇列),并且指定執行任務的方式(異步dispatch_async,同步dispatch_sync)

2.3.2.2 GCD的創建

  • 使用dispatch_queue_create來創建佇列物件,傳入兩個引數,第一個引數表示佇列的唯一識別符號,可為空,第二個引數用來表示串行佇列(DISPATCH_QUEUE_SERIAL)或并發佇列(DISPATCH_QUEUE_CONCURRENT),
// 串行佇列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 并發佇列
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
  • 主佇列:主佇列負責在主執行緒上調度任務,如果在主執行緒上已經有任務正在執行,主佇列會等到主執行緒空閑后再調度任務,通常是回傳主執行緒更新UI的時候使用,dispatch_get_main_queue()
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 耗時操作放在這里
3
      dispatch_async(dispatch_get_main_queue(), ^{
          // 回到主執行緒進行UI操作
3
      });
  });
  • 全域并發佇列:全域并發佇列是就是一個并發佇列,是為了讓我們更方便的使用多執行緒,dispatch_get_global_queue(0, 0)
//全域并發佇列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//全域并發佇列的優先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高優先級
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)優先級
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低優先級
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺優先級
//iOS8開始使用服務質量,現在獲取全域并發佇列時,可以直接傳0
dispatch_get_global_queue(0, 0);
  • 同步/異步任務創建方式

同步(sync)使用dispatch_sync來表示,

異步(async)使用dispatch_async,

任務就是將要在執行緒中執行的代碼,將這段代碼用block封裝好,

    // 同步執行任務
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任務放在這個block里
        NSLog(@"我是同步執行的任務");

    });
    // 異步執行任務
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 任務放在這個block里
        NSLog(@"我是異步執行的任務");

    });

2.3.2.3 GCD的方法

由于有多種佇列(串行/并發/主佇列)和兩種執行方式(同步/異步),所以他們之間可以有多種組合方式,

  • 串行同步 DISPATCH_QUEUE_SERIAL

執行完一個任務,再執行下一個任務,不開啟新執行緒,

/** 串行同步 */
- (void)syncSerial {

    // 串行佇列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    // 同步執行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步3   %@",[NSThread currentThread]);
        }
    });
}

輸出結果:

串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}
  • 串行異步 DISPATCH_QUEUE_SERIAL
    開啟新執行緒,但因為任務是串行的,所以還是按順序執行任務,
/** 串行異步 */
- (void)asyncSerial {

    // 串行佇列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    // 同步執行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行異步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行異步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行異步3   %@",[NSThread currentThread]);
        }
    });
}

輸入結果:

串行異步1   {number = 3, name = (null)}
串行異步1   {number = 3, name = (null)}
串行異步1   {number = 3, name = (null)}
串行異步2   {number = 3, name = (null)}
串行異步2   {number = 3, name = (null)}
串行異步2   {number = 3, name = (null)}
串行異步3   {number = 3, name = (null)}
串行異步3   {number = 3, name = (null)}
串行異步3   {number = 3, name = (null)}
  • 并發同步 DISPATCH_QUEUE_CONCURRENT
    因為是同步的,所以執行完一個任務,再執行下一個任務,不會開啟新執行緒,
/** 并發同步 */
- (void)syncConcurrent {
    // 并發佇列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    // 同步執行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發同步3   %@",[NSThread currentThread]);
        }
    });
}

輸入結果:

并發同步1   {number = 1, name = main}
并發同步1   {number = 1, name = main}
并發同步1   {number = 1, name = main}
并發同步2   {number = 1, name = main}
并發同步2   {number = 1, name = main}
并發同步2   {number = 1, name = main}
并發同步3   {number = 1, name = main}
并發同步3   {number = 1, name = main}
并發同步3   {number = 1, name = main}
  • 并發異步DISPATCH_QUEUE_CONCURRENT
    任務交替執行,開啟多執行緒,
/** 并發異步 */
- (void)asyncConcurrent {

    // 并發佇列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    // 同步執行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發異步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發異步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并發異步3   %@",[NSThread currentThread]);
        }
    });
}

輸入結果:

并發異步1   {number = 3, name = (null)}
并發異步2   {number = 4, name = (null)}
并發異步3   {number = 5, name = (null)}
并發異步1   {number = 3, name = (null)}
并發異步2   {number = 4, name = (null)}
并發異步3   {number = 5, name = (null)}
并發異步1   {number = 3, name = (null)}
并發異步2   {number = 4, name = (null)}
并發異步3   {number = 5, name = (null)}
  • 主佇列同步dispatch_get_main_queue dispatch_async

如果在主執行緒中運用這種方式,則會發生死鎖,程式崩潰,

/** 主佇列同步 */
- (void)syncMain {


    // 主佇列
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列同步3   %@",[NSThread currentThread]);
        }
    });
}
  • 主佇列異步 dispatch_get_main_queue dispatch_sync

在主執行緒中任務按順序執行,

/** 主佇列異步 */
- (void)asyncMain {

    // 主佇列
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列異步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列異步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主佇列異步3   %@",[NSThread currentThread]);
        }
    });
}

輸出結果:

主佇列異步1   {number = 1, name = main}
主佇列異步1   {number = 1, name = main}
主佇列異步1   {number = 1, name = main}
主佇列異步2   {number = 1, name = main}
主佇列異步2   {number = 1, name = main}
主佇列異步2   {number = 1, name = main}
主佇列異步3   {number = 1, name = main}
主佇列異步3   {number = 1, name = main}
主佇列異步3   {number = 1, name = main}
  • GCD執行緒之間通訊

開發中需要在主執行緒上進行UI的相關操作,通常會把一些耗時的操作放在其他執行緒,比如說圖片檔案下載等耗時操作,

當完成了耗時操作之后,需要回到主執行緒進行UI的處理,這里就用到了執行緒之間的通訊,

 - (IBAction)communicationBetweenThread:(id)sender {

    // 異步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 耗時操作放在這里,例如下載圖片,(運用執行緒休眠兩秒來模擬耗時操作)
        [NSThread sleepForTimeInterval:2];
        NSString *picURLStr = @"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";
        NSURL *picURL = [NSURL URLWithString:picURLStr];
        NSData *picData = [NSData dataWithContentsOfURL:picURL];
        UIImage *image = [UIImage imageWithData:picData];

        // 回到主執行緒處理UI
        dispatch_async(dispatch_get_main_queue(), ^{
            // 在主執行緒上添加圖片
            self.imageView.image = image;
        });
    });
}
  • GCD柵欄 dispatch_barrier_async
    當任務需要異步進行,但是這些任務需要分成兩組來執行,第一組完成之后才能進行第二組的操作,這時候就用了到GCD的柵欄方法dispatch_barrier_async
- (IBAction)barrierGCD:(id)sender {

    // 并發佇列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    // 異步執行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"柵欄:并發異步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"柵欄:并發異步2   %@",[NSThread currentThread]);
        }
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"------------barrier------------%@", [NSThread currentThread]);
        NSLog(@"******* 并發異步執行,但是34一定在12后面 *********");
    });

    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"柵欄:并發異步3   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"柵欄:并發異步4   %@",[NSThread currentThread]);
        }
    });
}

上面代碼的列印結果如下,開啟了多條執行緒,所有任務都是并發異步進行,但是第一組完成之后,才會進行第二組的操作,

柵欄:并發異步1   {number = 3, name = (null)}
柵欄:并發異步2   {number = 6, name = (null)}
柵欄:并發異步1   {number = 3, name = (null)}
柵欄:并發異步2   {number = 6, name = (null)}
柵欄:并發異步1   {number = 3, name = (null)}
柵欄:并發異步2   {number = 6, name = (null)}
 ------------barrier------------{number = 6, name = (null)}
******* 并發異步執行,但是34一定在12后面 *********
柵欄:并發異步4   {number = 3, name = (null)}
柵欄:并發異步3   {number = 6, name = (null)}
柵欄:并發異步4   {number = 3, name = (null)}
柵欄:并發異步3   {number = 6, name = (null)}
柵欄:并發異步4   {number = 3, name = (null)}
柵欄:并發異步3   {number = 6, name = (null)}
  • GCD延時執行 dispatch_after
    當需要等待一會再執行一段代碼時,就可以用到這個方法了:dispatch_after,
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 5秒后異步執行
    NSLog(@"我已經等待了5秒!");
});
GCD實作代碼只執行一次
使用dispatch_once能保證某段代碼在程式運行程序中只被執行1次,可以用來設計單例,
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"程式運行程序中我只執行了一次!");
});
  • GCD快速迭代 dispatch_apply
    GCD有一個快速迭代的方法dispatch_apply,dispatch_apply可以同時遍歷多個數字,
- (IBAction)applyGCD:(id)sender {

    NSLog(@"

************** GCD快速迭代 ***************

");

    // 并發佇列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // dispatch_apply幾乎同時遍歷多個數字
    dispatch_apply(7, queue, ^(size_t index) {
        NSLog(@"dispatch_apply:%zd======%@",index, [NSThread currentThread]);
    });
}

輸出結果:

dispatch_apply:0======{number = 1, name = main}
dispatch_apply:1======{number = 1, name = main}
dispatch_apply:2======{number = 1, name = main}
dispatch_apply:3======{number = 1, name = main}
dispatch_apply:4======{number = 1, name = main}
dispatch_apply:5======{number = 1, name = main}
dispatch_apply:6======{number = 1, name = main}
  • GCD佇列組dispatch_group_async
    異步執行幾個耗時操作,當這幾個操作都完成之后再回到主執行緒進行操作,就可以用到佇列組了,

佇列組有下面幾個特點:

  • 所有的任務會并發的執行(不按序),
  • 所有的異步函式都添加到佇列中,然后再納入佇列組的監聽范圍,
  • 使用dispatch_group_notify函式,來監聽上面的任務是否完成,如果完成, 就會呼叫這個方法,

佇列組示例代碼:

	 //創建一個佇列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //創建一個佇列組
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download image1 start");
        //下載圖片1
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 =[UIImage imageWithData:data];
        NSLog(@"download image1 end");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download image2 start");
        //下載圖片2
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image2 =[UIImage imageWithData:data];
        NSLog(@"download image2 end");
    });
    
    //當這個佇列組的所有佇列全部完成,就會收到這個訊息
    dispatch_group_notify(group, queue, ^{
        NSLog(@"download all images");
        //合成新圖片
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        //在主執行緒顯示
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });

列印結果:

佇列組:有一個耗時操作完成!
佇列組:有一個耗時操作完成!
佇列組:前面的耗時操作都完成了,回到主執行緒進行相關操作

2.3.3 NSOperation

2.3.3.1 介紹

  • 配合使用NSOperation和NSOperationQueue也能實作多執行緒編程

    • 先將需要執行的操作封裝到一個NSOperation物件中

    • 然后將NSOperation物件添加到NSOperationQueue中

    • 系統會自動將NSOperationQueue中的NSOperation取出來

    • 將取出的NSOperation封裝的操作放到一條新執行緒中執行

  • NSOperation的子類(抽象類,并不具備封裝操作的能力,必須使用它的子類)

    • NSInvocationOperation

    • NSBlockOperation

    • 自定義子類繼承NSOperation,實作內部相應的方法

2.3.3.2 NSInvocationOperation

  • 呼叫start方法開始執行操作,一旦執行操作就會呼叫run方法

  • 默認情況下,呼叫了start方法后并不會開一條新執行緒去執行操作,而是在當前執行緒同步執行操作,只有NSOperation放到一個NSOperationQueue中,才會異步執行,

 - (void)testNSInvocationOperation {
    // 創建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    // 開始執行操作
    [invocationOperation start];
}

 - (void)invocationOperation {
    NSLog(@"NSInvocationOperation包含的任務,沒有加入佇列========%@", [NSThread currentThread]);
}

2.3.3.3 NSBlockOperation

把任務放到NSBlockOperation的block中,然后start,(只要NSBlockOperation封裝的運算元大于1,就會異步執行,)

 - (void)testNSBlockOperation {
    // 把任務放到block中
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation包含的任務,沒有加入佇列========%@", [NSThread currentThread]);
    }];

    [blockOperation start];
}
  • 但是NSBlockOperation有一個方法addExecutionBlock:,通過這個方法可以讓NSBlockOperation實作多執行緒,
- (void)testNSBlockOperationExecution {
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation運用addExecutionBlock主任務========%@", [NSThread currentThread]);
    }];

    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務1========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務2========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation運用addExecutionBlock方法添加任務3========%@", [NSThread currentThread]);
    }];
    [blockOperation start];
}

列印結果:

NSBlockOperation運用addExecutionBlock========{number = 1, name = main}
addExecutionBlock方法添加任務1========{number = 3, name = (null)}
addExecutionBlock方法添加任務3========{number = 5, name = (null)}
addExecutionBlock方法添加任務2========{number = 4, name = (null)}

2.3.3.4 NSOperationQueue

  • NSOperationQueue的作用

    • NSOperation可以呼叫start方法來執行任務,默認同步
    • 如果將NSOperation添加到NSOperationQueue(操作佇列)中,系統會自動異步執行NSOperation中的操作
  • NSOperationQueue只有種佇列:主佇列其他佇列,其他佇列包含了串行并發

  • 佇列的創建

主佇列

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

其他佇列

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  • 添加任務到佇列addOperationaddOperationWithBlock
- (void)testOperationQueue {
    // 創建佇列,默認并發
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 創建操作,NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];
    // 創建操作,NSBlockOperation
    //blockOperationWithBlock 添加任務
    NSBlockOperation *blockOperation = [NSBlockOperation 	blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperation把任務添加到佇列======%@", [NSThread currentThread]);
        }
    }];

//addOperation 添加任務
    [queue addOperation:invocationOperation];
    [queue addOperation:blockOperation];
}


 - (void)invocationOperationAddOperation {
    NSLog(@"invocationOperation===aaddOperation把任務添加到佇列====%@", [NSThread currentThread]);
}
  • 運用最大并發數實作串行NSOperationQueue
- (void)testMaxConcurrentOperationCount {
    // 創建佇列,默認并發
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 最大并發數為1,串行
    queue.maxConcurrentOperationCount = 1;

    // 最大并發數為2,并發
//    queue.maxConcurrentOperationCount = 2;


    // 添加操作到佇列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任務添加到佇列1======%@", [NSThread currentThread]);
        }
    }];
    // 添加操作到佇列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任務添加到佇列2======%@", [NSThread currentThread]);
        }
    }];

    // 添加操作到佇列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任務添加到佇列3======%@", [NSThread currentThread]);
        }
    }];
}

運行結果如下,當最大并發數為1的時候,雖然開啟了執行緒,但是任務是順序執行的,所以實作了串行,

你可以嘗試把上面的最大并發數變為2,會發現任務就變成了并發執行,

addOperationWithBlock把任務添加到佇列1======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列1======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列1======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列2======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列2======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列2======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列3======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列3======{number = 3, name = (null)}
addOperationWithBlock把任務添加到佇列3======{number = 3, name = (null)}
  • 取消操作cancelAllOperationscancel
//取消所有操作
- (void)cancelAllOperations
//取消單個操作
 - (void)cancel
  • 暫停或繼續操作setSuspended.suspended
    • 當佇列呼叫了佇列掛起的方法( self.queue.suspended =
      YES;或者[self.queue setSuspended:YES];),佇列里的執行方法立即停止,但是有一點需要注意的是,當block操作中,佇列掛起是不起作用的,它是無法停止的,必須操作執行結束后才會生效,
// 暫停佇列
[self.queue setSuspended:YES];
或者
self.queue.suspended = YES;
  • 判斷佇列是否暫停:isSuspended
 - (BOOL)isSuspended
  • NSOperationQueue設定佇列監聽與依賴

NSOperation有一個非常好用的方法,就是操作依賴,可以從字面意思理解:某一個操作(operation2)依賴于另一個操作(operation1),只有當operation1執行完畢,才能執行operation2,這時,就是操作依賴大顯身手的時候了,

 NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"down1---%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"down2---%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"down3---%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
       
        NSLog(@"down4---%@",[NSThread currentThread]);
    }];
    
    
    //設定依賴(op1和op3執行完之后才執行2)
    [op3 addDependency:op1];
    [op3 addDependency:op4];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];

    
    //監聽一個操作的執行完成
    [op3 setCompletionBlock:^{
        NSLog(@"執行完成");
    }];

  • NSOperationQueue佇列間的資料通信
    先創建了一個普通佇列,在普通佇列里執行倆個操作,當子執行緒的圖片都下載下來后,回歸主執行緒將其顯示在UI界面上,
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    __block UIImage *image1;
    NSBlockOperation *downloadw1 = [NSBlockOperation blockOperationWithBlock:^{
        
        //下載圖片1
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image1 =[UIImage imageWithData:data];
    }];
    
    __block UIImage *image2;
    NSBlockOperation *downloadw2 = [NSBlockOperation blockOperationWithBlock:^{
        
        //下載圖片2
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image2 =[UIImage imageWithData:data];
        
    }];
    
    
    NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
        
        //合成新圖片
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];
    
    [combine addDependency:downloadw1];
    [combine addDependency:downloadw2];
    
    [queue addOperation:downloadw1];
    [queue addOperation:downloadw2];
    [queue addOperation:combine]; 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/200536.html

標籤:其他

上一篇:設計ADuC845低噪聲信號采集版

下一篇:電商專案——商城業務-首頁——第三章——中篇

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more