1. 程式人生 > >多執行緒以及底層實現

多執行緒以及底層實現

一.概念

  • 什麼是程序
    • 程序是指在系統中正在執行的一個應用程式
    • 每個程序之間的是獨立的,每個程序均執行在其專用且受保護的記憶體空間內
    • 一個程序至少要有一個執行緒
  • 什麼是執行緒
    • 一個執行緒要執行任務,必須得有執行緒
    • 一個程序(程式)的所有任務都線上程中執行的
    • 一個執行緒執行任務是序列的,也就是說一個執行緒,同一時間內,只能執行一個任務
  • 多執行緒原理
    • 同一時間,CPU只能處理1條執行緒,只有一條執行緒在工作(執行)
    • 多執行緒併發(同時)執行,其實質是CPU快速的在多執行緒之間排程(切換)
  • 如果執行緒過多,會怎樣?
    • CPU在N多條執行緒中排程,會消耗大量的cpu資源
    • 每條執行緒被排程執行的頻率越低(執行緒的執行效率低)
  • 多執行緒的優點
    • 能適當提高程式的執行效率
    • 能適當提高資源的利用率(CPU 記憶體利用率等)
  • 多執行緒的缺點
    • 建立執行緒是有開銷的,iOS下主要成本包括:核心資料結構(大約1KB)、棧空間(子執行緒512KB、主執行緒1MB,也可以使用-setStackSize:設定,但必須是4K的倍數,而且最小是16K),建立執行緒大約需要90毫秒的建立時間
    • 如果開啟大量的執行緒,會降低程式的效能
    • 程式越多CPU的執行緒上的開銷就越大
    • 程式設計更加複雜:執行緒之間的通訊,多執行緒的資料共享
  • 什麼是主執行緒
    • 一個ios程式執行後,會開啟一條執行緒,稱為主執行緒,或者是UI執行緒
  • 主執行緒的主要作用
    • 顯示和重新整理UI介面
    • 處理UI事件(比如點選事件,滾動事件,拖拽事件等)
  • 主執行緒的使用注意
    • 別將比較耗時的操作放在主執行緒中,會導致UI介面的卡頓
    • 將耗時操作放在子執行緒(後臺執行緒,非主執行緒)

二.多執行緒的4種方案

這裡寫圖片描述

  • 1.C語言的POSIX介面:#include

+ (NSThread *)mainThread; // 獲得主執行緒
- (BOOL)isMainThread; // 是否為主執行緒
+ (BOOL)isMainThread; // 是否為主執行緒```
- 其他用法
  ```objc//建立NSThread物件
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:nil object:nil];
    //獲取當前執行緒
NSThread *thread1 = [NSThread currentThread]; //設定執行緒的名字 thread.name = @"這是設定執行緒名字的"; [thread setName:@"這也是設定執行緒的名字的"];``` - 執行緒狀態 ![這裡寫圖片描述](https://img-blog.csdn.net/20160615133614638) - 互斥鎖 - @synchronized(鎖物件) { // 需要鎖定的程式碼 } 注意:鎖定1份程式碼只用1把鎖(同個物件),用多把鎖是無效的 - 優點:能有效防止因多執行緒搶奪資源造成的額資料安全問題 - 缺點:需要消耗大量的CPU資源 - 使用前提:多執行緒同一時間搶奪同一塊資源 - 互斥鎖就是使用了執行緒同步技術.就是多執行緒在按順序執行 - OC在定義屬性時有nonatomic和atomic兩種選擇 atomic:原子屬性,為setter方法加鎖(預設就是atomic) nonatomic:非原子屬性,不會為setter方法加鎖 ###1.常用的方法 ```objc //在主執行緒中執行任務 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; //YES表示等Selector執行完後才執行後面 - (void)performSelectorOnMainThread:(SEL)aSelector 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)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait //在後臺執行任務(相當於開闢了一個子執行緒) - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg //類方法,開闢一條子執行緒執行任務 [NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"hello"]; <div class="se-preview-section-delimiter"></div>

//開啟子執行緒,並且要通過手動開啟執行緒,否則無法執行任務的

// 建立執行緒
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"Alloc"];
    // 開啟執行緒,將執行緒新增到執行緒可排程池裡,等待CPU的排程
    [thread start];




<div class="se-preview-section-delimiter"></div>

//執行緒睡眠

//睡眠1秒
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
//執行緒睡眠1秒
[NSThread sleepForTimeInterval:1.0];




<div class="se-preview-section-delimiter"></div>

//執行緒退出,千萬不要在主執行緒退出,否則程式就會出現各種問題,包括奔潰,黑屏,不能響應

[NSThread exit];




<div class="se-preview-section-delimiter"></div>

2.執行緒的取消

 // 建立執行緒--> 新建狀態
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
    // 開啟執行緒--> 將執行緒新增到可排程執行緒池中,等待CPU排程
    [thread start];
    // 睡
    [NSThread sleepForTimeInterval:0.2];
    // 取消執行緒執行,取消執行緒僅僅是給執行緒打上一個被取消的標記
    // 要實現真正取消執行緒的執行需要線上程內部的關鍵節點進行判斷
    [thread cancel];
    NSLog(@"over");




<div class="se-preview-section-delimiter"></div>

3.執行緒的優先順序

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:@"---"];
    thread.name = @"執行緒A";
    //設定執行緒的優先順序,範圍是0到1
    thread.threadPriority = 1.0;
    [thread start];




<div class="se-preview-section-delimiter"></div>

4.執行緒棧區的大小

// 不管主執行緒還是子執行緒,棧區大小預設都是512kb.
    //    NSLog(@"stackSize = %zd",[NSThread currentThread].stackSize / 1024);
    // 最小的棧區大小是16kb,必須是4的整數倍,建議不要修改棧區大小,使用預設即可.
    [NSThread currentThread].stackSize = 1024 * 1024;
    NSLog(
    @"stackSize = %zd",[NSThread currentThread].stackSize / 1024)




<div class="se-preview-section-delimiter"></div>

5.多執行緒訪問資料導致的資料異常的處理

多個執行緒訪問資料的時候會導致資料異常,這時候我們要使用互斥鎖

// 先保證單條執行緒能正確工作:能夠買完所有票
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.ticket = 20;
    // 建立執行緒
    NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    threadA.name = @"美女A";
    // 開啟執行緒
    [threadA start];

    // 建立執行緒
    NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    threadB.name = @"美女B";
    // 開啟執行緒
    [threadB start];
}
- (void)saleTicket {
    while (YES)
    {
            // 睡
            [NSThread sleepForTimeInterval:1.0];
            // 使用互斥鎖:可以保證鎖住的程式碼同一時間只有一個執行緒訪問.
            // 使用互斥鎖鎖住的程式碼要儘可能少,鎖住關鍵節點的程式碼即可.
            // [[NSUserDefaults standardUserDefaults] synchronize];
            //@synchronized

           // self:鎖物件,可以是任意NSObject類的物件.
           // 注意點:鎖物件必須是所有執行緒物件能同時訪問的物件.
           // 如果只有一個地方使用到互斥鎖,一般鎖物件就是self`,避免再建立一個物件.
           // NSObject *lockObj = [[NSObject alloc] init];
        @synchronized(self)
        {
            // 判斷是否有剩餘的票數
            if(self.ticket > 0)
            {
                // 如果有,則賣一張
                self.ticket --;
                NSLog(@"%@賣了一張票,剩餘的票數:%zd",[NSThread currentThread].name, self.ticket);
                continue;
            }
        }
        // 沒有票,則提示使用者票沒了
        NSLog(@"票沒了");
        break;
    }
    NSLog(@"over");
}




<div class="se-preview-section-delimiter"></div>

拓展:

•nonatomic 與 atomic
1)iOS中還有一種鎖 原子鎖atomic
2)nonatomic 非原子屬性 (執行緒不安全),
3)atomic 原子屬性,預設都是"原子"屬性 (執行緒安全),也不能保證資料寫入的正確性 4)原子屬性,也是一個多執行緒技術,setter/getter函式是一個原子操作,如果多執行緒同時呼叫setter
時,不會出現某一個執行緒執行完setter所有語句之前,另一個執行緒就開始執行setter,相當於函式頭尾 加了鎖. 這樣的話併發訪問效能會比較低.
提示:設定原子屬性後,不要自己去寫原子屬性的setter方法
原因:原子屬性預設的setter方法中,使用了“128位自旋鎖”,效能比互斥鎖高,但是同樣消耗性 能




<div class="se-preview-section-delimiter"></div>
  • ##3.C語言的GCD(效能好,程式碼更精簡)
    • 什麼是GCD
    • 全稱是 Grand Central Dispatch,可譯為“牛逼的中樞排程器”
    • 純C語言,提供了非常多強大的函式
    • GCD的優勢
    • GCD是蘋果公司為多核的並行運算提出的解決方案
    • GCD會自動利用更多的CPU核心(比如雙核、四核)
    • GCD會自動管理執行緒的生命週期(建立執行緒、排程任務、銷燬執行緒)
    • 程式設計師只需要告訴GCD想要執行什麼任務,不需要編寫任何執行緒管理程式碼
    • GCD的有四種佇列:序列佇列,並行佇列,全域性佇列,主佇列.
    • GCD有兩種操作:非同步操作,同步操作.
    • 非同步操作dispatch_async,”會併發執行”,無法確定任務的執行順序
    • 同步操作dispatch_sync,”會依次順序執行”,能夠決定任務的執行順序
    • 主佇列可以看成序列佇列,如果在主執行緒中使用同步操作,那就會造成執行緒阻塞
    • 一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒,執行緒是操作
      系統可識別的最小執行和排程單位.

1.CGD的常用使用

注意:在主執行緒中,執行同步操作,裡面的任務將不會執行(阻塞),同步非同步決定是否可以開子執行緒,佇列決定任務是怎麼執行的.

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    [self gcdDemo1];//cgd的同步和非同步執行任務.
//    [self gcdDemo2];
//    [self gcdDemo3];  
}
//cgd的同步和非同步執行任務.
-(void)gcdDemo1
{
    void (^gcdBlock)() = ^()
    {
        NSLog(@"這是簡單的block,%@",[NSThread currentThread]);
    };
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, gcdBlock);//開啟子執行緒
    dispatch_async(queue, gcdBlock);//不開啟子執行緒
}
//gcd的常用寫法(精簡版)
- (void)gcdDemo2
{
    //在全域性佇列非同步執行
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"async---%@",[NSThread currentThread]);
    });
    //在主佇列中非同步執行.(沒有開啟子執行緒)
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"async---%@",[NSThread currentThread]);
    });
    //在主佇列同步執行
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"sync---%@",[NSThread currentThread]);
    });
}
//執行緒間的通訊
- (void)gcdDemo3
{
    //在非同步開啟子執行緒
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"這是耗時任務---%@",[NSThread currentThread]);
        //非同步在主執行緒執行任務
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主執行緒重新整理UI--%@",[NSThread currentThread]);
        });
    });
}




<div class="se-preview-section-delimiter"></div>

2.CGD中的序列佇列

/// 序列佇列非同步執行
/// 提問:會不會開執行緒? 是否是順序執行?
/// 問答:會開執行緒  開多條 是
- (void)gcdDemo3{
    for (int i = 0; i < 10; i++) {
        // 引數1:佇列的名稱
        dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
        // 將任務新增到佇列中,並指定執行任務的函式
        dispatch_async(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
}
/// 序列佇列同步執行
/// 提問:會不會開執行緒? 是否是順序執行?
/// 問答:不會開執行緒  是
- (void)gcdDemo1 {
    // 建立序列佇列
    // 引數1:佇列的名稱
    dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; ++i) {
        // 將任務新增到佇列中,並指定執行任務的函式
        dispatch_sync(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
}
/// 序列佇列非同步執行
/// 提問:會不會開執行緒? 開幾條執行緒? 是否是順序執行?
/// 問答:會開執行緒  開1條   是  全對
- (void)gcdDemo2 {
    // 建立序列佇列
    // 引數1:佇列的名稱
    dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; ++i) {
        // 將任務新增到佇列中,並指定執行任務的函式
        dispatch_async(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
}




<div class="se-preview-section-delimiter"></div>

3.GCD中的併發佇列

/// 併發佇列同步執行
/// 提問:會不會開執行緒? 開幾條? 是否是順序執行?
/// 答案:不會開  順序
- (void)gcdDemo1 {
    // 建立序列佇列
    // 引數1:佇列的名稱
    for (int i = 0; i < 10; ++i) {
        // 將任務新增到佇列中,並指定執行任務的函式
        dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
}
/// 序列佇列非同步執行
/// 提問:會不會開執行緒? 開幾條執行緒? 是否是順序執行?
/// 問答:會開執行緒  不知道,由底層執行緒池決定   不是
- (void)gcdDemo2 {
    // 建立序列佇列
    // 引數1:佇列的名稱
    dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5; ++i) {
        // 將任務新增到佇列中,並指定執行任務的函式
        dispatch_async(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"下載xxxx=%@",[NSThread currentThread]);
    NSLog(@"下載B=%@",[NSThread currentThread]);
    dispatch_async(queue, ^{
        NSLog(@"下載C=%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"下載A=%@",[NSThread currentThread]);
    });
}




<div class="se-preview-section-delimiter"></div>

4.GCD中的主佇列(特殊的序列佇列)

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"start");
    [self gcdDemo3];
    NSLog(@"end");
}

///主佇列同步執行不死鎖
- (void)gcdDemo3 {
    // 獲得主佇列
    dispatch_queue_t queue =  dispatch_get_main_queue();
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"over = %@",[NSThread currentThread]);
        // 同步任務
        dispatch_sync(queue, ^{
            NSLog(@"%@---%zd",[NSThread currentThread],100);
        });
    });
}

///主佇列同步執行
- (void)gcdDemo2 {
    // 獲得主佇列
    dispatch_queue_t queue =  dispatch_get_main_queue();
    // 同步任務
    // 主佇列中的任務必須在主執行緒空閒的時才會執行.
    // 主佇列中新增同步任務會造成死鎖.
    dispatch_sync(queue, ^{
        NSLog(@"%@---%zd",[NSThread currentThread],100);
    });
    NSLog(@"over");
}
//主佇列非同步執行
- (void)gcdDemo1 {
    // 獲得主佇列
    dispatch_queue_t queue =  dispatch_get_main_queue();
    for (int i = 0; i < 5; ++i) {
        // 非同步任務
        dispatch_async(queue, ^{
            NSLog(@"%@---%zd",[NSThread currentThread],i);
        });
    }
}




<div class="se-preview-section-delimiter"></div>

5.GCD中的全域性佇列

//全域性佇列非同步執行
//全域性佇列特點跟併發佇列是一樣的
// dispatch_get_global_queue:獲得全域性佇列不需要關心什麼時候銷燬
// 自己建立的併發佇列需要在不使用的時候銷燬
// 一般開發第三方框架時會是用自定義併發佇列.
- (void)gcdDemo1 {
    // 獲得主佇列
//    Flags that are reserved for future use. Always specify 0 for this parameter
    // 引數1:
    // 引數2:保留給未來使用,永遠給個0
    dispatch_queue_t queue =  dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 10; ++i) {
        // 非同步任務
        dispatch_async(queue, ^{
            NSLog(@"%@---%zd",[NSThread currentThread],i);
        });
    }
    // 在ARC中不允許呼叫release方法
//    dispatch_release(queue);
}




<div class="se-preview-section-delimiter"></div>

6.CGD中的dispatch_barrier_async,barrier非同步的使用

使用 dispatch_barrier_async 新增的 block 會在之前新增的 block 全部執行結束之後,統一在同一個執行緒順序執行,從而保證對非執行緒安全的物件進行正確的操作!

- (void)viewDidLoad {
    [super viewDidLoad];
    // 建立併發佇列
    queue  = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
    // dispatch_barrier_async:一定是要使用自定義併發佇列
//     queue  = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 20; ++i) {
        [self loadImage:i];
    }
}
/// 載入index索引指定圖片
// 主要用於在多個非同步操作完成之後,統一對非執行緒安全的物件進行更新
- (void)loadImage:(NSInteger)index{
    dispatch_async(queue, ^{
        // 獲得圖片名稱
        NSString *imageName = [NSString stringWithFormat:@"%zd.jpg",index % 9];
        // 獲得圖片路徑
        NSString *filePath = [[NSBundle mainBundle] pathForResource:imageName ofType:nil];
        // 載入圖片
        UIImage *image = [UIImage imageWithContentsOfFile:filePath];
        NSLog(@"下載第%zd張圖片,%@",index,[NSThread currentThread]);
        dispatch_barrier_async(queue, ^{
            NSLog(@"第%ld張圖片下載完成=%@,",(long)index,[NSThread currentThread]);
            // 將圖片新增到陣列中
            // NSMutableArray不是執行緒安全的類
            // NSMutableDictionary也不是執行緒安全的類的
            // 凡是帶有mutable單詞的類都不是執行緒安全.
            [self.images addObject:image];
        });
    });
}




<div class="se-preview-section-delimiter"></div>

7.dispatch_after操作和dispatch_once

1.dispatch_after

    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));                                 
    //延時操作,這裡是開啟了子執行緒的 
    dispatch_after(when, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"延遲操作是子執行緒:%@",[NSThread currentThread]);
        NSLog(@"這是延遲操作!!!");
    });   




<div class="se-preview-section-delimiter"></div>

2.dispatch_once

//使用dispatch_once實現單例,以及和互斥鎖實現單例的比較

(dispatch_once實現的單例比互斥鎖實現的單例效率高)

// dispatch_once {}
// 1.要提供一個全域性的訪問點
// 2.在這個專案中,只會有一個例項物件
+(instancetype)sharedHttpTool {
    static id instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 在這裡寫的程式碼在整個專案執行過程中只會執行一次
        NSLog(@"是一次嗎");
        instance = [[self alloc] init];
    });
    return instance;
}

// 使用互斥鎖實現單例
+ (instancetype)sharedSync {
    static id instance = nil;
    @synchronized(self) {
        if (instance == nil) {
            instance = [[self alloc] init];
        }
    }
    return instance;
}




<div class="se-preview-section-delimiter"></div>

8.排程組

- (void)gcdDemo1 {
    // 組物件
    dispatch_group_t group = dispatch_group_create();
    // 佇列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"下載圖片1=%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:1.5];
        NSLog(@"下載圖片2=%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2.5];
        NSLog(@"下載圖片3=%@",[NSThread currentThread]);
    });
    // dispatch_group_notify:當組裡面的所有任務執行完畢,會在主佇列中新增任務.並執行任務
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"更新UI %@",[NSThread currentThread]);
    });

    NSLog(@"come here");

//    dispatch_group_async(group, dispatch_get_main_queue(), ^{
//        NSLog(@"更新UI %@",[NSThread currentThread]);;
//    });
}




<div class="se-preview-section-delimiter"></div>

9.dispatch_apple的使用

NSArray *arr = @[@1,@2,@3,@4,@5];
    CFTimeInterval begin1 = CFAbsoluteTimeGetCurrent();
    dispatch_apply(arr.count, dispatch_get_global_queue(0, 0), ^(size_t index)
    {
        NSLog(@"%@,%@",arr[index],[NSThread currentThread]);

    });
    CFTimeInterval begin2 = CFAbsoluteTimeGetCurrent();
    NSLog(@"%lf",begin1 - begin2);





<div class="se-preview-section-delimiter"></div>
  • 4.Objective-C的NSOperation和NSOperationQueue(基於GCD)
    ###示例程式碼:
    1.用start新增任務和開啟執行緒
//新增任務和執行緒的開啟
- (void)opDemo1
{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
    //用start開啟開啟執行緒是在當前執行緒中執行任務的
    [op start];
}
- (void)test
{
    NSLog(@"it is test method-->%@",[NSThread currentThread]);
}




<div class="se-preview-section-delimiter"></div>

2.operation新增操作和開啟執行緒

- (void)opDemo2
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //設定佇列的併發數
    //如果設定為1,那麼就變成了序列佇列
    queue.maxConcurrentOperationCount = 2;
    for (int i = 0; i < 10; i ++) {
        //新增操作
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test:) object:nil];
        //往佇列中新增操作就-->開啟了非同步(除了在mainQueue中不開啟子執行緒)
         [queue addOperation:op];
    }
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"這是block操作-->%@",[NSThread currentThread] );
    }];
    [[[NSOperationQueue alloc] init] addOperation:op];
}




<div class="se-preview-section-delimiter"></div>
 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主執行緒
        NSLog(@"下載1------%@", [NSThread currentThread]);
    }];

    // 新增額外的任務(在子執行緒執行)
    [op addExecutionBlock:^{
        NSLog(@"下載2------%@", [NSThread currentThread]);
    }];

    [op addExecutionBlock:^{
        NSLog(@"下載3------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
        NSLog(@"下載4------%@", [NSThread currentThread]);
    }];

    [op start];

3.執行緒間通訊

//執行緒間的通訊
- (void)opDemo3
{
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"這是耗時操作--%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"UI更新--%@",[NSThread currentThread]);
        }];
    }];
}

4.佇列的掛起,開啟以及取消佇列操作

//控制佇列的掛起和開啟的按鈕點選事件
- (IBAction)pauseAndResume {
    if(self.queue.operationCount == 0) {
        NSLog(@"佇列中沒有操作");
        return;
    }
    // 設定佇列的掛起狀態
    // setter  = !getter
    self.queue.suspended = !self.queue.isSuspended;
    // 掛起佇列不會影響正在執行的操作
    // operationCount中包含沒有執行完畢的所有操作.
    // 佇列如果是掛起的,再往佇列中新增操作,也不會執行
    if (self.queue.suspended) {
        NSLog(@"暫停 = %zd",self.queue.operationCount);
    } else {
        NSLog(@"繼續 = %zd",self.queue.operationCount);
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    for (int i = 0; i < 20; ++i)
    {
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"%@--%d", [NSThread currentThread],i);
        }];
        [self.queue addOperation:op];
    }
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
        // 設定最大併發數
        _queue.maxConcurrentOperationCount = 2;
    }
    return _queue;
}

5.任務之間新增依賴

- (void)op1
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *bop = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"111bop,%@",[NSThread currentThread]);
    }];
    //往bop任務中新增額外的任務
    [bop addExecutionBlock:^{
        NSLog(@"2222---%@",[NSThread currentThread]);
    }];
    NSInvocationOperation *iop = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(top) object:nil];
    NSInvocationOperation *iop2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(top2) object:nil];
    //必須先新增依賴,再把任務新增到佇列中
    [bop addDependency:iop];
    [iop addDependency:iop2];
    [queue addOperation:bop];
    //新增任務到佇列中
    [queue addOperation:iop];
    [queue addOperation:iop2];  
}