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 是非線程安全的,若是出現兩個線程在同一時間向數組中添加對象,會出現程序崩潰的狀況async

  • 解決辦法函數

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]);
}
相關文章
相關標籤/搜索