深度使用 iOS多线程

深度使用 iOS多线程 

  1. GCD的队列类型
    1. 串行(Serial)队列
    2. 并行(ConCurrent)队列 
    3. 主队列 
    4. 全局队列
  2. GCD的使用
    1. 队列组
    2. enter & leavel
    3. 信号量
      1. dispatch_group_t 和 dispatch_semaphore_create 结合使用
      2. dispatch_semaphore_signal
    4. Barrier 异步
    5. 同步任务的作用
  3. GCD和NSOperation的区别
  4. 相关

 

GCG 的队列类型 

一、串行(Serial)队列 

 

  • 每次只能执行一个任务

  • 以 先进先出 的方式,顺序调度队列中的任务执行(FIF0)

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

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

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

串行队列,同步执行

dispatch_queue_t queue = dispatch_queue_create("FirstSerialQueue", DISPATCH_QUEUE_SERIAL);
  
    dispatch_sync(queue, ^{
        for (int i = 0; i<5; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"+++++++%@",[NSThread currentThread]);
        }
    });
    NSLog(@"打印第一个完毕");

    dispatch_sync(queue, ^{
        for (int i = 0; i<5; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"打印第二个完毕");
 
+++++++<NSThread: 0x1d4076400>{number = 1, name = main}
...
打印第一个完毕
------<NSThread: 0x1d4076400>{number = 1, name = main}
...
打印第二个完毕

串行队列同步执行,顺序执行没意义

串行队列,异步执行

NSLog(@"===============");

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

for (int i = 0; i < 5; ++i) {
    dispatch_sync(q, ^{
        NSLog(@"%@ - %d", [NSThread currentThread], i);
    });
}
NSLog(@"-----------------");
===============
-----------------
<NSThread: 0x1d7063b40>{number = 8, name = (null)} - 0
<NSThread: 0x1d7063b40>{number = 8, name = (null)} - 1
<NSThread: 0x1d7063b40>{number = 8, name = (null)} - 2
<NSThread: 0x1d7063b40>{number = 8, name = (null)} - 3
<NSThread: 0x1d7063b40>{number = 8, name = (null)} - 4

串行队列异步执行,先执行主线程任务,再执行子线程的任务,同类线程按照FIFO顺序执行

 

二、并行(ConCurrent)队列 

 

  • 以 先进先出 的方式,并发调度队列中的任务执行
  • 如果当前调度的任务是 同步(sync) 执行的, 会等待执行任务完成后,在调度新的任务。
  • 如果当前调度任务是 异步(async) 执行的,同时底层线程池有可用线程资源,会再新的线程调度后续任务执行。

并行队列,同步执行 <=> 串行队列,同步执行

NSLog(@"===============");
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5; ++i) {
        dispatch_sync(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }
NSLog(@"-----------------");

===============
<NSThread: 0x1d0261580>{number = 1, name = main} - 0
<NSThread: 0x1d0261580>{number = 1, name = main} - 1
<NSThread: 0x1d0261580>{number = 1, name = main} - 2
<NSThread: 0x1d0261580>{number = 1, name = main} - 3
<NSThread: 0x1d0261580>{number = 1, name = main} - 4
-----------------

并行队列,异步执行

NSLog(@"===============");
dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 5; ++i) {
        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }
NSLog(@"-----------------");

===============
-----------------
<NSThread: 0x1d0261580>{number = 9, name = (null)} - 0
<NSThread: 0x1d0261580>{number = 9, name = (null)} - 2
<NSThread: 0x1d0261580>{number = 10, name = (null)} - 3
<NSThread: 0x1d0261580>{number = 8, name = (null)} - 1
<NSThread: 0x1d0261580>{number = 11, name = (null)} - 4

 

四、全局队列 

 

是系统为了方便程序员开发提供,其工作表现与 并发队列 一致

全局队列 和 并行队列 的区别

  • 全局队列

    • 没有名称
    • 无论 MRC & ARC 都不需要考虑释放
    • 日常开发中,建议使用""全局队列"
  • 并行队列

    • 有名字,和 NSThread 的 name 属性作用类似
    • 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象
    • dispatch_barrier 必须使用自定义的并发队列
    • 开发第三方框架时,建议使用并发队列

全局队列 异步任务

// 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);

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

NSLog(@"=====");

运行效果与并发队列相同

 

三、主队列 

 

  • 专门用来在主线程上调度任务的队列
  • 不会开启线程
  • 以 先进先出 的方式,在 主线程空闲时 才会调度队列中的任务在主线程执行
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

主队列获取 

dispatch_queue_t queue = dispatch_get_main_queue();
  • 主队列是负责在主线程调度任务的
  • 会随着程序启动一起创建
  • 主队列只需要获取不用创建

主队列,异步执行 

NSLog(@"-----");
dispatch_queue_t queue = dispatch_get_main_queue();

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

NSLog(@"=====");

/// 执行结果
-----
---> 0,1,2,3,4
=====
<NSThread: 0x1d406b940>{number = 1, name = main} - 0,1,2,3,4

在 主线程空闲时才会调度队列中的任务在主线程执行

主队列,同步执行 

    // 1. 队列
    dispatch_queue_t q = dispatch_get_main_queue();

    NSLog(@"=====");

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

    NSLog(@"-----");

主队列 和 主线程 相互等待会造成死锁

  • 并队列异步(子线程)调度主队列同步不死锁
/// 并行队列
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
    
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"死?");
        });
    };
    
    dispatch_async(queue, task);

主队列在 主线程空闲时 才会调度队列中的任务在主线程执行

 

GCD的使用 

 

多个接口请求数据,需要监听接口请求完成,才能进行下一步的操作,我们采用网络请求是异步的。那么我们要如何监听呢?

通常情况下,多个网络请求同时执行,等所有网络请求返回后,再进行下一步操作,刷新UI,我们会想到 dispatch_group_asyncdispatch_group_notify 结合使用。

 

队列组 

 

- (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");
}

单单使用 dispatch_group_async 和 dispatch_group_notify 当任务1、2、3其中添加使用 dispatch_after 延迟模拟网络请求,其实是有问题的。

 

enter & leavel 

 

// MARK: - 调度组 2 ///通过enter和leave 多线程并行
- (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]);
}

2017-05-18 11:02:24.233  Request_2,<NSThread: 0x60800027ab80>{number = 3, name = (null)}
2017-05-18 11:02:24.233  Request_3,<NSThread: 0x60800027ab80>{number = 3, name = (null)}
2017-05-18 11:02:25.329  Request_1,<NSThread: 0x600000261ac0>{number = 1, name = main}
2017-05-18 11:02:25.330  完成,<NSThread: 0x60800026eb40>{number = 4, name = (null)}

 

信号量 

 

dispatch_semaphore_create   创建一个semaphore
dispatch_semaphore_signal   发送一个信号
dispatch_semaphore_wait    等待信号

简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。

 

dispatch_group_t 和 dispatch_semaphore_create 结合使用

 

///通过semaphore 多线程并行
- (void)example3 {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_t group = dispatch_group_create();
    
    ///初使值为0的semaphore
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    
    dispatch_group_async(group, queue, ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
                       
            dispatch_get_main_queue(), ^{
                ///可以理解为:信号量的值加一
                dispatch_semaphore_signal(sem);
                NSLog(@"Request_1,%@", [NSThread currentThread]);
        });
        ///可以理解为:信号量的值减一
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(sem);
            NSLog(@"Request_2,%@", [NSThread currentThread]);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"Request_3,%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"请求完成,%@", [NSThread currentThread]);
    });
}
Request_2
Request_3
Request_1
任务均完成,刷新界面

相关

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。

信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal

就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),

调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;

当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主

没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,

所以就一直等下去。

 

dispatch_semaphore_signal 使用

 

///FIFO
- (void)example5 {
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
    dispatch_queue_t queue = dispatch_queue_create("testBlock", NULL);
    
    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"1");
            dispatch_semaphore_signal(sem);
        });
        
    });
    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2");
            dispatch_semaphore_signal(sem);
        });
        
    });
    
    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"3");
            dispatch_semaphore_signal(sem);
        });
    });
    
    dispatch_async(queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"4");
            dispatch_semaphore_signal(sem);
        });
    });

}

2017-05-18 10:49:57.246  [1441:96225] 1
2017-05-18 10:49:57.247  [1441:96225] 2
2017-05-18 10:49:57.247  [1441:96225] 3
2017-05-18 10:49:57.248  [1441:96225] 4

 

dispatch_semaphore

 

  1. dispatch_semaphore_create
dispatch_semaphore_t  dispatch_semaphore_create(long value);

传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。 值得注意的是,这里的传入的参数 value >= 0,否则dispatch_semaphore_create会返回NULL。

  1. dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

这个函数会使传入的信号量dsema的值减1;

这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了, 且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。 如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

  1. dispatch_semaphore_signal

dispatch_semaphore_signal的返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理 的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。

  1. timeout

在设置timeout时,比较有用的两个宏:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER

  • DISPATCH_TIME_NOW  表示当前;

  • DISPATCH_TIME_FOREVER  表示遥远的未来;

创建 dispatch_time_t 类型的变量有两种方法,dispatch_time 和 dispatch_walltime。

  • 利用创建 dispatch_time 创建 dispatch_time_t 类型变量的时候一般也会用到这两个变量。
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。

///表示当前时间向后延时一秒为timeout的时间。
例如:dispatch_time_t  t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);

 

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;
}

操作 photoList 是非线程安全的

- (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);
    });
}

由于操作 photoList 是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况

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

});

使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

 

同步任务的作用 

 

同步任务,可以让其他异步执行的任务,依赖某一个同步任务

例如: 在用户登录之后,再异步下载文件!

NSLog(@"1============");
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]);
});
NSLog(@"2============");

1============
<NSThread: 0x1d0071bc0>{number = 1, name = main}
2============
下载 A <NSThread: 0x1d2e71d40>{number = 9, name = (null)}
下载 B <NSThread: 0x1d42780c0>{number = 8, name = (null)}
  • 代码改造,让登录也在异步执行
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);

登录 <NSThread: 0x1d2475100>{number = 9, name = (null)}
下载 A <NSThread: 0x1d2475100>{number = 9, name = (null)}
下载 B <NSThread: 0x1c4068dc0>{number = 10, name = (null)}

 

GCD和NSOperation的区别 

 

GCD是基于C的api,NSOperation属于Object-C类,操作队列是GCD实现Object-C API。

相当于与GCD

  1. NSOperation拥有更多的函数,具体查看api。
  1. 在NSOperationQueue中,可以建立NSOperation之间的依赖的关系。
  1. 配合KVO,可以检测Operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCancled)。

4.NSOperation可以管理并发,NSOperation之间的优先级。

相对于NSOperation

GCD主要与Block结合使用,代码简洁高效。

GCD也可以实现复杂的多线程应用,只是相对于NSOperation而言要复杂。

具体使用,根据需求而定。

这两者直接有什么区别呢?

  1. GCD是底层的C语言构成的API, 而NSOperationQueue及相关对象是objc对象。在GCD中,在队列中执行的是Block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择。
  1. 在NSOperationQueue中,我们随时可以取消已经设定要准备执行的任务,GCD可以,但是需要更多复杂的代码。
  1. NSOperation能够方便设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但是前者会直到后者执行完毕后再执行。
  1. 我们能将KVO应用在Operation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效掌握我们执行的后台任务。
  1. 在NSOperation中,我们能设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分Block任务的优先级,也需要大量的复杂代码。
  1. 我们能够对Operation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,能够在其之上添加更多自定制的功能。

总得来说,Operation Queue 提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。

操作队列提供了在GCD中不那么容易复制的有用特性,最重要一个就是可以取消队列中的任务;而且操作队列在管理操作间的依赖关系方面也容易一些。另一面GCD赋予你更多的控制权力以及操作队列中所不能使用的底层函数

 

相关 

 

1、GCD的认识

2、多个网络请求并发执行、顺序执行

3、把握AFNet网络请求完成的正确时机

4、关于dispatch_semaphore的使用

发布了40 篇原创文章 · 获赞 20 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章