GCD的一些使用

2018-11-27  本文已影响7人  wwwwwwdi

本片文字意在记录gcd的一些用法

1. dispatch_semaphore

相关方法:
dispatch_semaphore_create() //创建信号量,设置能同时执行的最大线程数
dispatch_semaphore_wait() //信号量-1
dispatch_semaphore_signal() //信号量+1

dispatch_semaphore_wait方法:
Decrement the counting semaphore. If the resulting value is less than zero, this function waits for a signal to occur before returning.
信号量减1,如果结果小于0,会阻塞当前线程等待新信号的出现。
如果为DISPATCH_TIME_FOREVER,则会一直等待
dispatch_semaphore_signal方法:
Increment the counting semaphore. If the previous value was less than zero, this function wakes a thread currently waiting in dispatch_semaphore_wait
信号量加1,如果之前的信号量小于0,则会唤醒dispatch_semaphore_wait方法中等待着的线程

使用的方法:先降后升
先创建信号量,设置能同时执行的最大线程数。如果为1,则同时可执行的线程就是1个,等进入到耗时操作1的时候,先让信号量降低,然后处理完成之后,再调用signal方法使信号量升高,在别的耗时操作中,也是一样的逻辑。
那么,在耗时操作2开始的时候,由于耗时操作1中信号量为0,且等待时间为DISPATCH_TIME_FOREVER此时则会一直等待

常规操作如下:
/**
 * 常规dispatch_semaphore使用方法(多个网络请求,限制最大并发数量)
 */
- (void)dispatchSemaphoreTest1 {
    dispatch_queue_t queue = dispatch_queue_create(0, 0);
    dispatch_semaphore_t sema = dispatch_semaphore_create(2);
    
    NSLog(@"--------------------------dispatch begin-----------------");
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
            [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
                
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"--------------------------success1-----------------");
                dispatch_semaphore_signal(sema);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"--------------------------fail1-----------------");
                dispatch_semaphore_signal(sema);
            }];
            
        });
        
        NSLog(@"--------------------------dispatch async complete1-----------------");
    });
    
    dispatch_async(queue, ^{
        //Thread4: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0)
        //https://stackoverflow.com/questions/24337791/exc-bad-instruction-code-exc-i386-invop-subcode-0x0-on-dispatch-semaphore-dis
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
            [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
                
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"--------------------------success2-----------------");
                dispatch_semaphore_signal(sema);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"--------------------------fail2-----------------");
                dispatch_semaphore_signal(sema);
            }];
        });
        NSLog(@"--------------------------dispatch async complete2-----------------");
    });
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
        [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"--------------------------success3-----------------");
            dispatch_semaphore_signal(sema);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"--------------------------fail3-----------------");
            dispatch_semaphore_signal(sema);
        }];
        NSLog(@"--------------------------dispatch async complete3-----------------");
    });
    
    NSLog(@"--------------------------dispatch complete-----------------");
}

运行之后,竟然崩溃了!!
报错信息:

EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0)

查询了stackoverflow之后,发现此篇文章
此问题是使用了dispatch_group_enter,dispatch_group_leavedispatch_group_wait三个方法的途中,遇到的这个问题,他的写法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();

     for (...) {
        if (...) {
            dispatch_group_enter(group);
            dispatch_async(queue, ^{

               [self webserviceCall:url onCompletion:^{
                     dispatch_group_leave(group);
               }];
            });
        }
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
        dispatch_sync(queue, ^{
            // call completion handler passed in by caller
        });
    });

其中回答说:


是在使用的过程中,调用已经释放了的group才导致的这个问题。

受此文章启发,我考虑到上面的写法是否也是由于这个原因呢?
然后我把dispatch_semaphore_t作为当前类的属性,强引用一下,发现解决了这个问题。

    //只需要在前面创建semaphore和queue的时候,强引用一下,然后再后续的调用中,都修改为self.xxx即可
    self.sema = dispatch_semaphore_create(2);
    self.queue = dispatch_queue_create(0, 0);

2018年11月28日
今天我又试了一下:发现竟然又不崩溃了????
而且也无法复现那个问题了。。。excuse me ???


异步变同步

下面的实现是一种取巧的方法,达到了同步的效果,

- (void)testDispatchSemaphore {
NSLog(@"--------------------------begin-----------------");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t que = dispatch_get_global_queue(0, 0);

dispatch_async(que, ^{
NSLog(@"--------------------------async begin-----------------");
[NSThread sleepForTimeInterval:3];
dispatch_semaphore_signal(sema);
NSLog(@"--------------------------async complete-----------------");
});


dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(@"--------------------------complete-----------------");

}

//log如下:
--------------------------begin-----------------
--------------------------async begin-----------------
--------------------------async complete-----------------
--------------------------complete-----------------
这里有一个问题点:创建semaphore的时候的初始值问题

我们都知道,此semaphore值,就是用来限制最大并行线程的,也就是说如果传1,则运行的时候,同一时间只能处理一个线程。如果传4,则同时处理4个。那有没有考虑过,如果传0呢?
这里给出了苹果文档上的解释

Passing zero for the value is useful for when two threads need to reconcile the completion of a particular event.
Passing a value greater than zero is useful for managing a finite pool of resources, where the pool size is equal to the value.

大意好像是:
如果传0进去,则会使两个线程同时处理完成回调。(当两个线程需要使某个事件完成一致)
如果传大于0的值进去,会使队列资源池变为有限的,从而限制最大并发数

关于队列资源池:

详见Objective-C高级编程 iOS与OS X多线程和内存管理第三章

2. 多个异步操作结束之后,统一操作(dispatch_group)

用到的方法:
dispatch_group_enter()
dispatch_group_leave()
dispatch_group_notify()

注意:这里enter和leave要成对出现

- (void)dispatchGroupTest {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.fxeyeQueue.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_enter(group);
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            
            NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
            [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
                
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"--------------------------success1-----------------");
                dispatch_group_leave(group);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"--------------------------fail1-----------------");
                dispatch_group_leave(group);
            }];
            
            [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
                
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"--------------------------success2-----------------");
                dispatch_group_leave(group);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"--------------------------fail2-----------------");
                dispatch_group_leave(group);
            }];
        });
        
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"--------------------------notify-----------------");
    });
    
    NSLog(@"--------------------------complete-----------------");
    
}
//log如下:
14:08:58.014812+0800 testOperation[6098:474448] --------------------------complete-----------------
14:09:01.035264+0800 testOperation[6098:474448] --------------------------fail2-----------------
14:09:01.035430+0800 testOperation[6098:474448] --------------------------fail1-----------------
14:09:01.035564+0800 testOperation[6098:474778] --------------------------notify-----------------

也有另外一种写法:

把两个请求分别放到两个异步group操作中,即:
- (void)dispatchGroupTest {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.fxeyeQueue.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        
        NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
        dispatch_group_enter(group);
        [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"--------------------------success1-----------------");
            dispatch_group_leave(group);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"--------------------------fail1-----------------");
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_async(group, queue, ^{
        
        NSString *imgUrl = @"https://www.baidu.com/img/bd_logo1.png";
        dispatch_group_enter(group);
        [[AFHTTPSessionManager manager] GET:imgUrl parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
            
        } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"--------------------------success2-----------------");
            dispatch_group_leave(group);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"--------------------------fail2-----------------");
            dispatch_group_leave(group);
        }];
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"--------------------------notify-----------------");
    });
    
    NSLog(@"--------------------------complete-----------------");
    
}

从log上看,也是可以达到效果的,
但是!此种写法在时机使用中,遇到了一个问题,会造成整个APP卡死。。
然后Bugly报错说max operation count reached,思来想去也没找到问题。还请知道解决方法的大佬给予明示!thx~


今天又接着Google,终于找到了问题所在。受此文启发。
项目中代码是在WKWebview的didFinishNavigation方法中调用请求方法。
然后

/**
 * 两个请求结束之后,统一操作
 */
- (void)groupRequest {
    dispatch_group_t group = dispatch_group_create();
    self.group = group;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_group_enter(self.group);
    dispatch_group_enter(self.group);
    dispatch_group_async(self.group, queue, ^{
        [某网络请求方法 {
            dispatch_group_leave(self.group);
        }];
    });
    
    dispatch_group_async(self.group, queue, ^{
        [某网络请求方法 {
            dispatch_group_leave(self.group);
        }];
    });
    
    dispatch_group_notify(self.group, queue, ^{
        //获取到之后,刷新页面
        [self reloadViewWithModelData];
    });
}

由于webview的加载过程不可控,所以可能存在多次调用此方法的情况,然后就会创建很多的group,造成Bugly报出的max operation count reached问题,因此懒加载group理论上就能解决此问题。上面代码改为:

- (void)groupRequest {
    if (self.group == nil) {
        dispatch_group_t group = dispatch_group_create();
        self.group = group;
    }
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    ......
}

3. dispatch_barrier

其实添加依赖之类的操作,除了使用上面的两个, 还可以使用dispatch_barrier

notes:
但是有一点要先说明:
dispatch_barrier必须用在并发队列中,而且不能是获取的系统的全局队列,得是通过dispatch_queue_create自己创建的并发队列才可以

/**
 * dispatch_barrier,只在并发队列中才有用,而且不能是获取的系统的全局队列,得是通过dispatch_queue_create自己创建的并发队列才可以
 */
- (void)dispatchBarrierFunction {
    dispatch_queue_t queue = dispatch_queue_create("com.fxxxxxx.www", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"task1 -%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task2 -%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task3 -%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:5];
        NSLog(@"barrier");
    });
    NSLog(@"asynchronous");
    
    dispatch_async(queue, ^{
        NSLog(@"task4 -%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task5 -%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"task6 -%@",[NSThread currentThread]);
    });
}

log如下:

2018-11-28 17:41:56.986682+0800  asynchronous
2018-11-28 17:41:56.986849+0800  task1 -<NSThread: 0x600002ef63c0>{number = 7, name = (null)}
2018-11-28 17:41:56.986849+0800  task2 -<NSThread: 0x600002efd7c0>{number = 8, name = (null)}
2018-11-28 17:41:56.986885+0800  task3 -<NSThread: 0x600002e20e00>{number = 3, name = (null)}
2018-11-28 17:42:01.991930+0800  barrier
2018-11-28 17:42:01.992341+0800  task6 -<NSThread: 0x600002ef63c0>{number = 7, name = (null)}
2018-11-28 17:42:01.992397+0800  task5 -<NSThread: 0x600002e20e00>{number = 3, name = (null)}
2018-11-28 17:42:01.992341+0800  task4 -<NSThread: 0x600002efd7c0>{number = 8, name = (null)}

可以看到,barrier方法可以在中间设置一个阻拦,然后等上面的处理完成之后,再进行下面的操作!
但是!!!!如果操作中有网络请求等耗时操作呢?

/**
 * dispatch_barrier,只在并发队列中才有用,而且不能是获取的系统的全局队列,得是通过dispatch_queue_create自己创建的并发队列才可以
 * 可以看到,带有网络请求异步回调的情况,并不适用,可以使用dispatch_group_enter/dispatch_group_leave或者dispatch_semaphore_wait来实现
 */
- (void)dispatchBarrierTest {
    dispatch_queue_t queue = dispatch_queue_create("com.fxxxxxx.wwww", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [self afnRequestWithTag:1 delay:2 complete:^{
            
        }];
    });
    
    dispatch_async(queue, ^{
        [self afnRequestWithTag:2 delay:2 complete:^{
            
        }];
    });
    
    dispatch_async(queue, ^{
        [self afnRequestWithTag:3 delay:2 complete:^{
            
        }];
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier");
    });
    
    dispatch_async(queue, ^{
        [self afnRequestWithTag:4 delay:2 complete:^{
            
        }];
    });
    
    dispatch_async(queue, ^{
        [self afnRequestWithTag:5 delay:2 complete:^{
            
        }];
    });
    
    NSLog(@"--------------------------barrier complete-----------------");
}
log:
2018-11-28 17:44:43.410024+0800  --------------------------barrier complete-----------------
2018-11-28 17:44:43.410079+0800  barrier
2018-11-28 17:44:45.425016+0800  --------------------------fail3-----------------
2018-11-28 17:44:45.425200+0800  --------------------------fail2-----------------
2018-11-28 17:44:45.425346+0800  --------------------------fail1-----------------
2018-11-28 17:44:45.425454+0800  --------------------------fail4-----------------
2018-11-28 17:44:45.425556+0800  --------------------------fail5-----------------

可以看出来,并不管用了。。
所以,如果要在多个操作中请求接口,还是要用上面说的dispatch_group_enter/dispatch_group_leave或者dispatch_semaphore_wait来实现

附文中代码的Demo

参考

https://www.jianshu.com/p/29152d90cef7

上一篇下一篇

猜你喜欢

热点阅读