dispatch_semaphore和NSOperationQueue并发 (GCD 等)

From: http://www.liuchendi.com/2015/11/10/iOS/2_dispatch_semaphore%E5%92%8CNSOperationQueue%E5%B9%B6%E5%8F%91/

并发:同一个时间内运行多个任务。又叫共行性,是指处理多个同时性活动的能力。
并行:是指两个并发的任务同时发生。

并发不一定并行,并发不一定要同时发生。

举个例子:

有两个快递分别要送到目的地,有以下两种方案:

(1)一个快递员分别把两个快递送到对应的目的地。(并发)

(2)两个快递员同时送一个快递到目的地。(并行)

在iOS中,经常可以看见有这样的需求,就是一个方法要等另外一个方法执行完毕再做相对应的处理,比如说一些网络请求,需要根据上一个请求的返回值做相对应的处理再执行第二个请求,所以我们不能让两个请求同时去请求网络。下面就记录以下通过GCD和NSOperationQueue来控制并发。

##dispatch_semaphore

信号量是一个整型值并且具有初始计数值,信号量通常支持两个操作:通知和等待。当信号被通知的时候计数值会增加,当信号量在线程上等待的时候,必要的情况下线程会被阻塞掉,直至信号被通知时计数值大于0,然后线程会减少这个计数继续工作。

GCD中又3个信号量有关的操作:

dispatch_semaphore_create    信号量创  

dispatch_semaphore_signal    发送通知  

dispatch_semaphore_wait     信号量等待  



__block dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("testBlock", NULL);
dispatch_async(queue, ^{      
    for (int i = 0 ; i < 100; i++) {
        NSLog(@"i的值是:%d",i);
    }
    dispatch_semaphore_signal(sem);
});   
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
for (int j = 0; j < 10; j ++) {
    NSLog(@"j的值是:%d",j);
}  

运行结果是:

##NSOperationQueue
在不添加依赖的情况下:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 10;

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{

    for (int i = 0; i < 1000; i++) {

        NSLog(@"执行并发队列1:%d",i);
    }
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){

    for (int i = 0; i < 1500; i++) {

        NSLog(@"执行并发队列2:%d",i);
    }
}];


[queue addOperation:operation1];
[queue addOperation:operation2];

运行结果是:

添加依赖控制后:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 10;

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{

    for (int i = 0; i < 1000; i++) {

        NSLog(@"执行并发队列1:%d",i);
    }
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){

    for (int i = 0; i < 1500; i++) {

        NSLog(@"执行并发队列2:%d",i);
    }
}];



[operation1 addDependency:operation2];   

[queue addOperation:operation1];
[queue addOperation:operation2];

运行结果是:


From: http://blog.csdn.net/Cloudox_/article/details/71107179

对计算机了解的都会知道信号量的作用,当我们多个线程要访问同一个资源的时候,往往会设置一个信号量,当信号量大于0的时候,新的线程可以去操作这个资源,操作时信号量-1,操作完后信号量+1,当信号量等于0的时候,必须等待,所以通过控制信号量,我们可以控制能够同时进行的并发数。

在网络请求的开发中,经常会遇到两种情况,一种是我在一个界面需要同时请求多种数据,比如列表数据、广告数据等,全部请求到后再一起刷新界面。另一种是我的请求必须满足一定顺序,比如必须先请求个人信息,然后根据个人信息请求相关内容。这些要求对于普通的操作是可以做到并发控制和依赖操作的,但是对于网络请求这种需要时间的请求来说,效果往往与预期的不一样,这时候就需要用信号量来做一个控制。

信号量是一个整数,在创建的时候会有一个初始值,这个初始值往往代表我要控制的同时操作的并发数。在操作中,对信号量会有两种操作:信号通知与等待。信号通知时,信号量会+1,等待时,如果信号量大于0,则会将信号量-1,否则,会等待直到信号量大于0。什么时候会大于零呢?往往是在之前某个操作结束后,我们发出信号通知,让信号量+1。

说完概念,我们来看看GCD中的三个信号量操作:

  • dispatch_semaphore_create:创建一个信号量(semaphore)
  • dispatch_semaphore_signal:信号通知,即让信号量+1
  • dispatch_semaphore_wait:等待,直到信号量大于0时,即可操作,同时将信号量-1

在使用的时候,往往会创建一个信号量,然后进行多个操作,每次操作都等待信号量大于0再操作,同时信号昂-1,操作完后将信号量+1,类似下面这个过程:

dispatch_semaphore_t sema = dispatch_semaphore_create(5);
for (100次循环操作) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        dispatch_semaphore_signal(sema);
    });
}

上面代码表示我要操作100次,但是控制允许同时并发的操作最多只有5次,当并发量达到5后,信号量就减小到0了,这时候wait操作会起作用,DISPATCH_TIME_FOREVER表示会永远等待,一直等到信号量大于0,也就是有操作完成了,将信号量+1了,这时候才可以结束等待,进行操作,并且将信号量-1,这样新的任务又要等待。

假设我们一个页面需要同时进行多个请求,他们之间倒是不要求顺序关系,但是要求等他们都请求完毕了再进行界面刷新或者其他什么操作。

这个需求我们一般可以用GCD的group和notify来做到:

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Request_1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Request_2");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"Request_3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    NSLog(@"任务均完成,刷新界面");
});

notify的作用就是在group中的其他操作全部完成后,再操作自己的内容,所以我们会看到上面三个内容都打印出来后,才打印界面刷新的内容。

但是当将上面三个操作改成真实的网络操作后,这个简单的做法会变得无效,为什么呢?因为网络请求需要时间,而线程的执行并不会等待请求完成后才真正算作完成,而是只负责将请求发出去,线程就认为自己的任务算完成了,当三个请求都发送出去,就会执行notify中的内容,但请求结果返回的时间是不一定的,也就导致界面都刷新了,请求才返回,这就是无效的。

要解决这个问题,我们就要用到上面说的信号量来操作了。

在每个请求开始之前,我们创建一个信号量,初始为0,在请求操作之后,我们设一个dispatch_semaphore_wait,在请求到结果之后,再将信号量+1,也即是dispatch_semaphore_signal。这样做的目的是保证在请求结果没有返回之前,一直让线程等待在那里,这样一个线程的任务一直在等待,就不会算作完成,notify的内容也就不会执行了,直到每个请求的结果都返回了,线程任务才能够结束,这时候notify也才能够执行。伪代码如下:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

有时候我们需要按照顺序执行多次请求,比如先请求到用户信息,然后根据用户信息中的内容去请求相关的数据,这在平常的代码中直接按照顺序往下写代码就可以了,但这里因为涉及到多线程之间的关系,就叫做线程依赖。

线程依赖用GCD做比较麻烦,建议用NSOperationQueue做,可以更加方便的设置任务之间的依赖。

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    [self request_A];
}];


NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    [self request_B];
}];


[operation2 addDependency:operation1];


NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation2, operation1] waitUntilFinished:NO];

一般的多线程操作这样做是可以的,线程2会等待线程1完成后再执行。但是对于网络请求,问题又来了,同样,网络请求需要时间,线程发出请求后即认为任务完成了,并不会等待返回后的操作,这就失去了意义。

要解决这个问题,还是用信号量来控制,其实是一个道理,代码也是一样的,在一个任务操作中:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[网络请求:{
        成功:dispatch_semaphore_signal(sema);
        失败:dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

/// 注意上述网络请求在主线程返回数据, 上述代码在主线程执行会死锁

还是去等待请求返回后,才让任务结束。而依赖关系则通过NSOperationQueue来实现。

其实归根结底,中心思想就是通过信号量,来控制线程任务什么时候算作结束,如果不用信号量,请求发出后即认为任务完成,而网络请求又要不同时间,所以会打乱顺序。因此用一个信号量来控制在单个线程操作内,必须等待请求返回,自己要执行的操作完成后,才将信号量+1,这时候一直处于等待的代码也得以执行通过,任务才算作完成。

通过这个方法,就可以解决由于网络请求耗时特性而带来的一些意想不到的多线程处理的问题。


版权所有:http://blog.csdn.net/cloudox_

参考资料:
1、http://www.cocoachina.com/ios/20170428/19150.html
2、http://blog.csdn.net/fhbystudy/article/details/25918451


From: https://www.jianshu.com/p/3d1eab375295

dispatch group 可以把并行执行的多个任务合成一组,于是调用者就可以知道这些任务何时才能全部执行完毕。例如,可以把一系列操作文件的任务或者网络请求的任务添加到 dispatch group 中,等全部完成这些耗时的任务之后再进行下一步操作。
创建 dispatch group 的方式如下:

dispatch_group_t dispatch_group_create(void);

dispatch group 就是个简单的数据结构,这种结构彼此之间没有什么区别,它不像派发队列,后者还有个用来区别身份的标识符。

想把任务编组,有两种方法。第一种是用下面这个函数:

dispatch_group_async(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);

它是普通 dispatch_async 函数的变体。group 参数用于表示执行的块所归属的组,queue 表示执行任务的队列。所以 dispatch_group_async 可以方便的向 dispatch group 中添加不同队列的不同任务。此函数中的 queue 既可以时串行队列,又可以时并行队列。然而,如果所有任务都排在同一个串行队列里,那么 dispatch group 的用处就不大了,因为只需要在提交完全部任务之后再提交一个块即可。

第二种能够指定任务所属的 dispatch group 的方法是使用下面这一对函数:

void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);

前者能够使 dispatch group 里正要执行的任务数递增,而后者则使之递减。所以,调用完 dispatch_group_enter 以后,必须有与之对应的 dispatch_group_leave 才行。如果调用 enter 之后,没有相应的 leave 操作,那么这一组任务就永远执行不完。在使用时,可以在向队列中添加任务时调用 enter,在任务执行完成之后合适的地方调用 leave

当像 group 中添加完所有的任务之后,有两种方式可以等待任务完成。第一种方法是使用下面的函数:

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

此函数有两个参数,group 是要等待的线程组,timeout 表示要等待的时间。timeout 参数表示函数在等待 dispatch group 执行完毕时,应该阻塞多久。如果执行 dispatch group 所需的时间小于 timeout,则返回 0,否则返回非 0 值。此参数也可以取常量 DISPATCH_TIME_FOREVER,这表示函数会一直等待 dispatch group 执行完,而不会超时。注意:dispatch_group_wait 是同步的,会阻塞当前线程,所以不能放在主线程执行。

第二种方式时使用下面函数:

void dispatch_group_notify(dispatch_group_t group,
        dispatch_queue_t queue,
        dispatch_block_t block);

dispatch_group_wait 不同的是该函数不会阻塞当前线程。开发着可以向此函数传入 block,等 dispatch group 执行完毕之后,block 会在指定的线程上运行。

使用 dispatch_group_wait 的例子:

  • object-c

    NSLog(@”— 开始设置任务 —-“);
    // 因为 dispatch_group_wait 会阻塞线程,所以创建一个新的线程,用来完成任务
    // 同时用异步的方式向新线程(tasksQueue)中添加任务
    dispatch_queue_t tasksQueue = dispatch_queue_create(“tasksQueue”, DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(tasksQueue, ^{

    // 真正用来完成任务的线程
    dispatch_queue_t performTasksQueue = dispatch_queue_create("performTasksQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    for (int i = 0; i < 3; i++) {
        // 入组之后的 block 会被 group 监听
        // 注意:dispatch_group_enter 一定和 dispatch_group_leave 要配对出现
        dispatch_group_enter(group);
        dispatch_async(performTasksQueue, ^{
            NSLog(@"开始第 %zd 项任务", i);
            [NSThread sleepForTimeInterval:(3 - i)];
            dispatch_group_leave(group);
            NSLog(@"完成第 %zd 项任务", i);
        });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"全部任务完成");
    });
    

    });
    NSLog(@”— 结束设置任务 —-“);

  • swift 3.0

    print(“— 开始设置任务 —-“);
    DispatchQueue.main.async {

    let group = DispatchGroup.init()
    let tasksQueue = DispatchQueue.init(label: "tasksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
    for i in 0..<3 {
        group.enter()
        tasksQueue.async {
            print("开始第 \(i) 项任务")
            sleep(UInt32(3 - i))
            print("结束第 \(i) 项任务")
            group.leave()
        }
    }
    //
    let result = group.wait(timeout: DispatchTime.distantFuture)
    if result == .success {
        print("全部任务完成")
    } else {
        print("任务执行超时")
    }
    

    }
    print(“— 开始设置任务 —-“);

输出如下:

— 开始设置任务 —-
— 结束设置任务 —-
开始第 0 项任务
开始第 1 项任务
开始第 2 项任务
完成第 2 项任务
完成第 1 项任务
完成第 0 项任务
全部任务完成

swift 4 代码 组, 当然信号量也是能实现这样的功能的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//
let group = DispatchGroup()
// let quebook = DispatchQueue(label: "com.boosj.usersavedata")
let quebook = DispatchQueue(label: "com.boosj.usersavedata", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: .inherit, target: nil)
var isSaveUser = false
var isSaveImage = false
quebook.async(group: group){
isSaveUser = true
// 这个和下面的leave 配合使用, 会等待手动控制结束
group.enter()
NetTool.shared.saveUserMessage(message: self.viewModel.userMessageMode, complete: { (success, error) in
printLog("信息保存成功")
group.leave()
})
}
quebook.async(group: group){
isSaveImage = true
group.enter()
NetTool.shared.saveUserHeadImage(image: self.viewModel.userMessageMode.userImage!, complete: { (success, error) in
printLog("图片保存成功")
group.leave()
})
}
group.notify(queue: DispatchQueue.main){
// 执行完成
printLog("全部执行完毕")
}
Author

陈昭

Posted on

2017-12-07

Updated on

2021-12-27

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Kommentare

You forgot to set the shortname for Disqus. Please set it in _config.yml.