IOS开发之多线程队列[转]

转自 http://www.cnblogs.com/junior-ios/p/4694508.html

 

串行队列

特点

  • 先进先出的方式,顺序调度队列中的任务执行
  • 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

队列创建

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] {number = 1, name = main} - 0
2015-07-13 00:44:58.244 testGCD线程[37988:581895] {number = 1, name = main} - 1
2015-07-13 00:44:58.244 testGCD线程[37988:581895] {number = 1, name = main} - 2
2015-07-13 00:44:58.244 testGCD线程[37988:581895] {number = 1, name = main} - 3
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {number = 1, name = main} - 4
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {number = 1, name = main} - 5
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {number = 1, name = main} - 6
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {number = 1, name = main} - 7
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {number = 1, name = main} - 8
2015-07-13 00:44:58.245 testGCD线程[37988:581895] {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 都不需要考虑释放
    • 日常开发中,建议使用全局队列
  • 并发队列
    • 有名字,和 NSThread 的 name 属性作用类似
    • 如果在 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]);
}

你可能感兴趣的:(IOS开发之多线程队列[转])