IOS開發之多線程隊列


串行隊列

特點

  • 先進先出的方式,順序調度隊列中的任務執行
  • 無論隊列中所指定的執行任務函數是同步還是異步,都會等待前一個任務執行完成后,再調度后面的任務

隊列創建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);

串行隊列演練

  • 串行隊列 同步執行
/**
 提問:是否開線程?是否順序執行?come here 的位置?
 */
- (void)gcdDemo1 {
    // 1. 隊列
    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);

    // 2. 執行任務
    for (int i = 0; i < 10; ++i) {
        NSLog(@"--- %d", i);

        dispatch_sync(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}
  • 串行隊列 異步執行
/**
 提問:是否開線程?是否順序執行?come here 的位置?
 */
- (void)gcdDemo2 {
    // 1. 隊列
    dispatch_queue_t q = dispatch_queue_create("itheima", NULL);

    // 2. 執行任務
    for (int i = 0; i < 10; ++i) {
        NSLog(@"--- %@ %d", [NSThread currentThread], i);

        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}

並發隊列

特點

  • 先進先出的方式,並發調度隊列中的任務執行
  • 如果當前調度的任務是同步執行的,會等待任務執行完成后,再調度后續的任務
  • 如果當前調度的任務是異步執行的,同時底層線程池有可用的線程資源,會再新的線程調度后續任務的執行

隊列創建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

並發隊列演練

  • 並發隊列 異步執行
/**
 提問:是否開線程?是否順序執行?come here 的位置?
 */
- (void)gcdDemo3 {

    // 1. 隊列
    dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);

    // 2. 執行任務
    for (int i = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}
  • 並發隊列 同步執行
/**
 提問:是否開線程?是否順序執行?come here 的位置?
 */
- (void)gcdDemo4 {

    // 1. 隊列
    dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);

    // 2. 執行任務
    for (int i = 0; i < 10; ++i) {
        dispatch_sync(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
        NSLog(@"---> %i", i);
    }

    NSLog(@"come here");
}

主隊列

特點

  • 專門用來在主線程上調度任務的隊列
  • 不會開啟線程
  • 先進先出的方式,在主線程空閑時才會調度隊列中的任務在主線程執行
  • 如果當前主線程正在有任務執行,那么無論主隊列中當前被添加了什么任務,都不會被調度

隊列獲取

  • 主隊列是負責在主線程調度任務的
  • 會隨着程序啟動一起創建
  • 主隊列只需要獲取不用創建
dispatch_queue_t queue = dispatch_get_main_queue();

主隊列演練

  • 主隊列,異步執行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self gcdDemo1];

    [NSThread sleepForTimeInterval:1];
    NSLog(@"over");
}

- (void)gcdDemo1 {

    dispatch_queue_t queue = dispatch_get_main_queue();

    for (int i = 0; i < 10; ++i) {
        dispatch_async(queue, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
        NSLog(@"---> %d", i);
    }

    NSLog(@"come here");
}


2015-07-13 00:44:57.241 testGCD線程[37988:581895] ---> 0
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 1
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 2
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 3
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 4
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 5
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 6
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 7
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 8
2015-07-13 00:44:57.242 testGCD線程[37988:581895] ---> 9
2015-07-13 00:44:57.242 testGCD線程[37988:581895] come here
2015-07-13 00:44:58.243 testGCD線程[37988:581895] over
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 0
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 1
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 2
2015-07-13 00:44:58.244 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 3
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 4
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 5
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 6
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 7
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 8
2015-07-13 00:44:58.245 testGCD線程[37988:581895] <NSThread: 0x7facd160e9a0>{number = 1, name = main} - 9

主線程空閑時才會調度隊列中的任務在主線程執行

  • 主隊列,同步執行
// MARK: 主隊列,同步任務
- (void)gcdDemo6 {
    // 1. 隊列
    dispatch_queue_t q = dispatch_get_main_queue();

    NSLog(@"!!!");

    // 2. 同步
    dispatch_sync(q, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

    NSLog(@"come here");
}

主隊列主線程相互等待會造成死鎖

同步任務的作用

同步任務,可以讓其他異步執行的任務,依賴某一個同步任務

例如:在用戶登錄之后,再異步下載文件!

- (void)gcdDemo1 {
    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"登錄 %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"下載 A %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"下載 B %@", [NSThread currentThread]);
    });
}
  • 代碼改造,讓登錄也在異步執行
- (void)gcdDemo2 {
    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

    void (^task)() = ^{
        dispatch_sync(queue, ^{
            NSLog(@"登錄 %@", [NSThread currentThread]);
        });

        dispatch_async(queue, ^{
            NSLog(@"下載 A %@", [NSThread currentThread]);
        });

        dispatch_async(queue, ^{
            NSLog(@"下載 B %@", [NSThread currentThread]);
        });
    };

    dispatch_async(queue, task);
}
  • 主隊列調度同步隊列不死鎖
- (void)gcdDemo3 {

    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

    void (^task)() = ^ {
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"死?");
        });
    };

    dispatch_async(queue, task);
}

主隊列在主線程空閑時才會調度隊列中的任務在主線程執行

Barrier 異步

  • 主要用於在多個異步操作完成之后,統一對非線程安全的對象進行更新
  • 適合於大規模的 I/O 操作

代碼演練

  • 准備工作
@interface ViewController () {
    // 加載照片隊列
    dispatch_queue_t _photoQueue;
}

@property (nonatomic, strong) NSMutableArray *photoList;
@end

- (NSMutableArray *)photoList {
    if (_photoList == nil) {
        _photoList = [[NSMutableArray alloc] init];
    }
    return _photoList;
}

NSMutableArray 是非線程安全的

  • viewDidLoad
- (void)viewDidLoad {
    [super viewDidLoad];

    _photoQueue = dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 20; ++i) {
        [self loadPhotos:i];
    }
}
  • 模擬下載照片並在完成后添加到數組
- (void)loadPhotos:(int)index {

    dispatch_async(_photoQueue, ^{
        [NSThread sleepForTimeInterval:1.0];

        NSString *fileName = [NSString stringWithFormat:@"%02d.jpg", index % 10 + 1];
        NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        [self.photoList addObject:image];
        NSLog(@"添加照片 %@", fileName);
    });
}

運行測試

  • 由於 NSMutableArray 是非線程安全的,如果出現兩個線程在同一時間向數組中添加對象,會出現程序崩潰的情況

  • 解決辦法

NSLog(@"添加照片 %@", fileName);
dispatch_barrier_async(_photoQueue, ^{
    [self.photoList addObject:image];
    NSLog(@"OK %@", [NSThread currentThread]);

});

使用 dispatch_barrier_async 添加的 block 會在之前添加的 block 全部運行結束之后,才在同一個線程順序執行,從而保證對非線程安全的對象進行正確的操作!

Barrier 工作示意圖

注意:dispatch_barrier_async 必須使用自定義隊列,否則執行效果和全局隊列一致

全局隊列

  • 是系統為了方便程序員開發提供的,其工作表現與並發隊列一致

全局隊列 & 並發隊列的區別

  • 全局隊列
    • 沒有名稱
    • 無論 MRC & ARC 都不需要考慮釋放
    • 日常開發中,建議使用全局隊列
  • 並發隊列
    • 有名字,和 NSThreadname 屬性作用類似
    • 如果在 MRC 開發時,需要使用 dispatch_release(q); 釋放相應的對象
    • dispatch_barrier 必須使用自定義的並發隊列
    • 開發第三方框架時,建議使用並發隊列

全局隊列 異步任務

/**
 提問:是否開線程?是否順序執行?come here 的位置?
 */
- (void)gcdDemo8 {
    // 1. 隊列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 執行任務
    for (int i = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }

    NSLog(@"come here");
}

運行效果與並發隊列相同

參數

  1. 服務質量(隊列對任務調度的優先級)/iOS 7.0 之前,是優先級

    • iOS 8.0(新增,暫時不能用,今年年底)
      • QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望最快完成-不能用太耗時的操作)
      • QOS_CLASS_USER_INITIATED 0x19, 用戶期望(希望快,也不能太耗時)
      • QOS_CLASS_DEFAULT 0x15, 默認(用來底層重置隊列使用的,不是給程序員用的)
      • QOS_CLASS_UTILITY 0x11, 實用工具(專門用來處理耗時操作!)
      • QOS_CLASS_BACKGROUND 0x09, 后台
      • QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 適配
    • iOS 7.0
      • DISPATCH_QUEUE_PRIORITY_HIGH 2 高優先級
      • DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認優先級
      • DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優先級
      • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台優先級
  2. 為未來保留使用的,應該永遠傳入0

結論:如果要適配 iOS 7.0 & 8.0,使用以下代碼:
dispatch_get_global_queue(0, 0);

延遲操作

// MARK: - 延遲執行
- (void)delay {
    /**
     從現在開始,經過多少納秒,由"隊列"調度異步執行 block 中的代碼

     參數
     1. when    從現在開始,經過多少納秒
     2. queue   隊列
     3. block   異步執行的任務
     */
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
    };
    // 主隊列
//    dispatch_after(when, dispatch_get_main_queue(), task);
    // 全局隊列
//    dispatch_after(when, dispatch_get_global_queue(0, 0), task);
    // 串行隊列
    dispatch_after(when, dispatch_queue_create("itheima", NULL), task);

    NSLog(@"come here");
}

- (void)after {
    [self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];

    NSLog(@"come here");
}

一次性執行

有的時候,在程序開發中,有些代碼只想從程序啟動就只執行一次,典型的應用場景就是“單例”

// MARK: 一次性執行
- (void)once {
    static dispatch_once_t onceToken;
    NSLog(@"%ld", onceToken);

    dispatch_once(&onceToken, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"一次性嗎?");
    });
    NSLog(@"come here");
}
  • dispatch 內部也有一把鎖,是能夠保證線程安全的!而且是蘋果公司推薦使用的
  • 以下代碼用於測試多線程的一次性執行
- (void)demoOnce {
    for (int i = 0; i < 10; ++i) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self once];
        });
    }
}

單例測試

單例的特點

  1. 在內存中只有一個實例
  2. 提供一個全局的訪問點

單例實現

// 使用 dispatch_once 實現單例
+ (instancetype)sharedSingleton {
    static id instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });

    return instance;
}

// 使用互斥鎖實現單例
+ (instancetype)sharedSync {
    static id syncInstance;

    @synchronized(self) {
        if (syncInstance == nil) {
            syncInstance = [[self alloc] init];
        }
    }

    return syncInstance;
}

面試時只要實現上面 sharedSingleton 方法即可

單例測試

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    long largeNumber = 1000 * 1000;

    // 測試互斥鎖
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSync];
    }
    NSLog(@"互斥鎖: %f", CFAbsoluteTimeGetCurrent() - start);

    // 測試 dispatch_once
    start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSingleton];
    }
    NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}

調度組

常規用法

- (void)group1 {

    // 1. 調度組
    dispatch_group_t group = dispatch_group_create();

    // 2. 隊列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 3. 將任務添加到隊列和調度組
    dispatch_group_async(group, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任務 1 %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任務 2 %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任務 3 %@", [NSThread currentThread]);
    });

    // 4. 監聽所有任務完成
    dispatch_group_notify(group, q, ^{
        NSLog(@"OVER %@", [NSThread currentThread]);
    });

    // 5. 判斷異步
    NSLog(@"come here");
}

enter & leavel

// MARK: - 調度組 2
- (void)group2 {
    // 1. 調度組
    dispatch_group_t group = dispatch_group_create();

    // 2. 隊列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // dispatch_group_enter & dispatch_group_leave 必須成對出現
    dispatch_group_enter(group);
    dispatch_group_async(group, q, ^{
        NSLog(@"任務 1 %@", [NSThread currentThread]);

        // dispatch_group_leave 必須是 block 的最后一句
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_group_async(group, q, ^{
        NSLog(@"任務 2 %@", [NSThread currentThread]);

        // dispatch_group_leave 必須是 block 的最后一句
        dispatch_group_leave(group);
    });

    // 4. 阻塞式等待調度組中所有任務執行完畢
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    // 5. 判斷異步
    NSLog(@"OVER %@", [NSThread currentThread]);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM