使用信号量同步网络请求


标签: objectvie-c 线程同步

问题

有两个网络请求A和B,放在一个队列中,要求A执行完,返回数据后,B开始执行。如A,B两个任务不是异步的,解决的方法有很多

使用NSBlockOperation和NSOperationQueue

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *blkA = [NSBlockOperation blockOperationWithBlock:^{
		NSLog(@"task A");
}];
NSBlockOperation *blkB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task B");
}];
[blkB addDependency:blkA];
[queue addOperations:@[blkA, blkB] waitUntilFinished:YES];

因为不异步的,所以blkA执行完,即开始执行blkB。但如果是网络请求,因为是异步的,所以当blkA将网络请求发出去以后,blkA即认为我负责的工作就完成了,blkA不会等待返回的数据,同样blkB也是如此。这样不能达到两个任务顺序执行。

GCD serial队列

dispatch_queue_t serailQueue = dispatch_queue_create("serailqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serailQueue, ^{
    NSLog(@"task A");
});
dispatch_async(serailQueue, ^{
    NSLog(@"task B");
});

如果两个任务都是异步的同样不能达到两个任务顺序执行的目的,哪怕是串行队列

GCD

GCD 队列按照先进先出的顺序执行任务,最选加入到队列里的block,最早执行。

GCD_Block执行顺序

  • Concurrent: tasks are dequeued in FIFO order, but run concurrently and can finish in any order.

  • Serial: tasks execute one at a time in FIFO order

并发: 任务以FIFO从序列中移除,然后并发运行,可以按照任何顺序完成。它会自动开启多个线程同时执行任务

串行: 任务以FIFO从序列中一个一个执行。一次只调度一个任务,队列中的任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)而且只会开启一条线程

不管使用什么队列,即使按照A,B这种顺序将任务加入到队列里,A最开始执行,因为是异步的,block块将请求发出去之后block块就返回了,也不会等待数据的返回。

正确的解决方法有两种:

  1. 在任务A的回调里获取到返回的数据之后,再发起任务B

  2. 使用信号量

第一种简单,直接,也不需要控制线程之间的关系,第二种需要控制线程之间的执行关系。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"task A");
        dispatch_semaphore_signal(semaphore);
    });

});

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore,  DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"task B");
        dispatch_semaphore_signal(semaphore);
    });
});

按照GCD队列的执行顺序,第一个block先加入到队列里,因此先执行。

  1. 任务A检查信号量,semaphore > 1, 将semaphore递减

  2. 发起网络请求,并返回

任务B的执行顺序:

  1. 任务B检查信号量,semaphore = 0, 因此等待任务A的网络请求结束

  2. 当任务A的结束时发送一个信号量,将semaphore加1

  3. semaphore大于1,任务B被调起执行,执行完毕将semaphore加1

dispatch_semaphore_wait为信号量的P操作,dispatch_semaphore_signal为信号量的V操作。P操作和V操作都是原子的递增和递减计数器,从而保证,两个线程之间的同步关系。信号量还控制了开辟的线程的最大的数量。

dispatch_after

dispatch_after延时执行任务,它不是将任务加入到队列里后在打定的时间执行任务,而是在执定的时间,将任务加入到队列里执行。

    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC));
        dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"task A");
            dispatch_semaphore_signal(semaphore);
        });
    });
    dispatch_sync(serailQueue, ^{
        NSLog(@"task B");
    });

这种情况下,dispatch_async添加到queue里的任务会立即返回,视为执行完,不管queue是何种类型队列,是使用dispatch_async或者是dispatch_sync,结果都一样。先输出task B,大约4秒后输入task A。此处应该是第一个block向系统标记:我有一段代码要在4秒后放到queue里执行。干完这件事后block就认为自己的任务完成,随即返回。然后在大约4秒后,系统将要执行的代码段放在queue里执行,这个时候task B早早的执行完了。

–EOF–

参考

由于disqus被墙,评论需要VPN哦。