多线程-GCD(二)

706 阅读5分钟

一、面试题

1、面试题一:信号量

下面代码中外面输出的是几

-(void)testDemo{

    __block int a = 1;

    while (a < 5) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"里面:a= %d -- %@",a,[NSThread currentThread]);
            a++;
        });
    }
    NSLog(@"外面:%d",a);
}

答案: 外面输出的是大于或者等于5

解释:

  • while循环中会不断网并发队列中添加任务,可能一个循环过后,添加到队列里面的任务还没执行,a还没有++,所以当a==5的时候while里面循环了多少次不确定
  • 但是有一点确定的是只有当a=5的时候才会终止循环
  • 当终止循环后,代码就会往下执行然后打印a,但是从终止循环到打印a的这段时间可能并发队列里面的任务有几个正好执行完了,这样就会导致在这段时间中a的值又变大了
  • 所以外面打印的a大于或者等于5

拓展:

这样桌会导致连个后果:

  • 浪费很多性能
  • 最终的a值无法确定

怎么解决这两个问题呢,我们用信号量方式来解决下:

-(void)testDemo{
    //创建时候可以设置信号量大小,设置的大小也可以认为是可创建线程的d多少
    dispatch_semaphore_t sem = dispatch_semaphore_create(2);
    __block int a = 1;
    while (a < 5) {
        //第一次循环时候相当于开辟一个线程,此时信号量那边为1,开辟完线程后,信号量那边可开辟线程就为0了
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"里面:a= %d -- %@",a,[NSThread currentThread]);
            a++;
            //这句代码是告诉信号量那边可以再开辟线程了,可以放开拦截了
            dispatch_semaphore_signal(sem);
        });
        /*dispatch_semaphore_wait是当可用信号量为0时候就开始拦截
         由于信号量只能为1,
         所以只有当第一个任务中dispatch_semaphore_signal执行完,
         这个地方的拦截才能放开,就形成了同步的现象
         */

        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"中间:a= %d -- %@",a,[NSThread currentThread]);
    }
    NSLog(@"外面:%d",a);
}

2、面试题二-栅栏函数

/**
 栅栏函数的演示说明:dispatch_barrier_sync/dispatch_barrier_async
 */
- (void)demo2{
    
    // 你全部下载完毕之后,我才能处理
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrentQueue1 = dispatch_queue_create("cooci_lg", DISPATCH_QUEUE_CONCURRENT);

    // 全局 -- 堵塞
    // 系统GG  -- 设计
//    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);

    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        // 请求token -- 线程安全
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
        }
    });
//
    dispatch_async(concurrentQueue1, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    /* 2. 栅栏函数 */
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    NSLog(@"加载那么多,喘口气!!!");
    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"日常处理3-%zd-%@",i,[NSThread currentThread]);
        }
    });
    NSLog(@"************起来干!!");
    
    dispatch_async(concurrentQueue1, ^{
        for (NSUInteger i = 0; i < 5; i++) {
            NSLog(@"日常处理4-%zd-%@",i,[NSThread currentThread]);
        }
    });
}
  • 栅栏函数其实是作为堵塞队列而存在的(同步函数是作为堵塞线程存在的)
  • 堵塞队列意思就是在栅栏函数之前的任务要全部执行完才开始执行栅栏函数
  • 并且栅栏函数执行完后才栅栏函数之后的任务
  • 但是栅栏函数不能用于全局并发队列,因为全局并发队列系统也在用, 假如用栅栏函数将全局并发队列堵塞住了,会导致系统运行不正常而导致崩溃

3、面试题三--可变数组 线程不安全 解决办法

不通过加锁的放松解决下面代码问题

- (void)demo3{

    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<2000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            [self.mArray addObject:image];

        });
    }
}

答案: 上面存在多个线程同时对可变数组操作,会造成崩溃,通过GCD解决办法就是在[self.mArray addObject:image];改完

dispatch_barrier_async(concurrentQueue, ^{
   [self.mArray addObject:image];
});

解释: 通过栅栏函数异步添加任务,会将这句代码以任务方式添加到队列中,而栅栏函数又有堵塞队列左右,所有每个栅栏函数都是一个个执行,在同一个队列中不会同时执行两个栅栏函数

4、面试题4--调度组

- (void)groupDemo{
    
    //创建调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);

    // SIGNAL
    dispatch_group_async(group, queue, ^{
        NSString *logoStr = @"http://p.qpic.cn/qqcourse/QFzQYCgCrxlq7n5Jats36WGb4wxzJIYmFplnUUgdxk4gy66xicbmGCDM5DXDuVNQP/";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    dispatch_group_async(group, queue1, ^{
        sleep(2);
        NSString *logoStr = @"https://www.baidu.com/img/baidu_resultlogo@2.png";
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
        UIImage *image = [UIImage imageWithData:data];
        [self.mArray addObject:image];
    });
    
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,1 * NSEC_PER_SEC));
    if (timeout == 0) {
        NSLog(@"回来了");
    }else{
        NSLog(@"等待中 -- 转菊花");
    }
    
    
    __block UIImage *newImage = nil;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"数组个数:%ld",self.mArray.count);
        for (int i = 0; i<self.mArray.count; i++) {
            UIImage *waterImage = self.mArray[i];
            newImage = [KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
        }
        self.imageView.image = newImage;
    });

}
// 这也是一种堵塞方式

 dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
 
 dispatch_time_t timeout:参数用来指定等待的时间
 
 这里的等待表示,一旦调用dispatch_group_wait函数,该函数就处理调用的状态而不返回值,只有当函数的currentThread停止,或到达wait函数指定的等待的时间,或Dispatch Group中的操作全部执行完毕之前,执行该函数的线程停止.     
 当指定timeout为DISPATCH_TIME_FOREVER时就意味着永久等待
 当指定timeout为DISPATCH_TIME_NOW时就意味不用任何等待即可判定属于Dispatch Group的处理是否全部执行结束
 如果dispatch_group_wait函数返回值不为0,就意味着虽然经过了指定的时间,但Dispatch Group中的操作并未全部执行完毕
 
 如果dispatch_group_wait函数返回值为0,就意味着Dispatch Group中的操作全部执行完毕

二、dispatch_source_t自定义定时器

@interface BYTimer : NSObject

+(BYTimer *)timerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(BYTimer *timer))block;

/// 开始
-(void)resume;

/// 取消定时器
-(void)cancel;

@end



@interface BYTimer ()

@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic) BOOL isRunning;
@property (copy, nonatomic) NSTimer *timer;

@property (copy, nonatomic)  void(^complateBlock)(BYTimer *timer);

@property (assign, nonatomic) NSTimeInterval timeInterval;

@end
@implementation BYTimer

+(BYTimer *)timerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(BYTimer *timer))block{

    BYTimer *timer = [[self alloc]init];
    timer.isRunning     = NO;
    timer.timeInterval = timeInterval;
    timer.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatch_get_main_queue());

    uint64_t interval = (uint64_t)(timeInterval * NSEC_PER_SEC);
    dispatch_source_set_timer(timer.source, DISPATCH_TIME_NOW, interval, 0);

    dispatch_source_set_event_handler(timer.source, ^{

        if (block) {
            block(timer);
        }
    });

    return timer;
}

/// 开始/暂停
-(void)resume{
    if (self.isRunning) {//开始
        self.isRunning = NO;
        dispatch_suspend(self.source);
    }else{//暂停
        self.isRunning = YES;
        dispatch_resume(self.source);
    }
}

-(void)cancel{//取消

    if (!self.isRunning) {
        dispatch_resume(self.source);
    }
    dispatch_source_cancel(self.source);
}