iOS多线程GCD简介(二)

在上一篇中,咱们主要讲了Dispatch Queue相关的内容。这篇主要讲一下一些和实际相关的使用实例,Dispatch Groups和Dispatch Semaphore。安全

dispatch_after

在咱们开发过程当中常常会用到在多少秒后执行某个方法,一般咱们会用这个- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay函数。不过如今咱们可使用一个新的方法。多线程

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        //do your task
    });

这样咱们就定义了一个延迟2秒后执行的任务。不过在这里有一点须要说明的是,不管你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay仍是dispatch_after这个方法。并非说在你指定的延迟后当即运行,这些方法都是基于单线程的,它只是将你延迟的操做加入到队列里面去。因为队列里面都是FIFO,因此必须在你这个任务以前的操做完成后才会执行你的方法。这个延迟只是大概的延迟。若是你在主线程里面调用这个方法,若是你主线程如今正在处理一个很是耗时的任务,那么你这个延迟可能就会误差很大。这个时候你能够再开个线程,在里面执行你的延迟操做。并发

//放到全局默认的线程里面,这样就没必要等待当前调用线程执行完后再执行你的方法
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //do your task
    });

dispatch_once

这个想必你们都很是的熟悉,这个在单例初始化的时候是苹果官方推荐的方法。这个函数能够保证在应用程序中只执行指定的任务一次。即便在多线程的环境下执行,也能够保证百分之百的安全。app

static id instance;
    static dispatch_once_t predicate;

    dispatch_once(&predicate, ^{
        //your init
    });

    return instance;
}

这里面的predicate必须是全局或者静态对象。在多线程下同时访问时,这个方法将被线程同步等待,直到指定的block执行完成。async

dispatch_apply

这个方法是执行循环次数固定的迭代,若是在并发的queue里面能够提升性能。好比一个固定次数的for循环函数

for (int i = 0; i < 1000; i ++) {
        NSLog(@"---%d---", i);
    }

若是只是在一个线程里面或者在一个串行的队列中是同样的,一个个执行。
如今咱们用dispatch_apply来写这个循环:性能

dispatch_apply([array count], defaultQueue, ^(size_t i) {
        NSLog(@"----%@---", array[i]);
    });
    NSLog(@"end");

这个方法执行后,它将像这个并发队列中不断的提交执行的block。这个i是从0开始的,最后一个是[array count] - 1测试

使用这个方法有几个注意点:spa

  1. 这个方法调用的时候会阻塞当前的线程,也就是上面的循环所有执行完毕后,才会输出end
  2. 在你使用这个任务进行操做的时候,你应该确保你要执行的各个任务是独立的,并且执行顺序也是可有可无的。
  3. 在你使用这个方法的时候,你仍是要权衡下总体的性能的,若是你执行的任务时间比线程切换的时间还短。那就得不偿失了。

dispatch_group

在实际开发中,咱们可能须要在一组操做所有完成后,才作其余操做。好比上传一组图片,或者下载多个文件。但愿在所有完成时给用户一个提示。若是这些操做在串行化的队列中执行的话,那么你能够很明确的知道,当最后一个任务执行完成后,就所有完成了。这样的操做也并木有发挥多线程的优点。咱们能够在并发的队列中进行这些操做,可是这个时候咱们就不知道哪一个是最后一个完成的了。这个时候咱们能够借助dispatch_group:线程

dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, defaultQueue, ^{
        //task1
        NSLog(@"1");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task2
        NSLog(@"2");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task3
        NSLog(@"3");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task4
        NSLog(@"4");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task5
        NSLog(@"5");
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

咱们首先建立一个group而后往里面加入咱们要执行的操做,在dispatch_group_notify这个函数里面添加所有完成的操做。上面代码执行的时候,输出的1,2,3,4,5的顺序是不必定的,可是输出的finish必定是在1,2,3,4,5以后。
对于添加到group的操做还有另一个方法:

dispatch_group_enter(group);
    dispatch_group_enter(group);

    dispatch_async(defaultQueue, ^{
        NSLog(@"1");
        dispatch_group_leave(group);
    });

    dispatch_async(defaultQueue, ^{
        NSLog(@"2");
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

咱们能够用dispatch_group_enter来表示添加任务,dispatch_group_leave来表示有个任务已经完成了。用这个方法必定要注意必须成双成对。

线程同步

在多线程中一个比较重要的东西就是线程同步的问题。若是多个线程只是对某个资源只是读的过程,那么就不存在这个问题了。若是某个线程对这个资源须要进行写的操做,那这个时候就会出现数据不一致的问题了。

使用dispatch_barrier_async

__block NSString *strTest = @"test";

    dispatch_async(defaultQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(defaultQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_async(defaultQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });

看看这个模拟的场景,咱们让各个线程去访问这个变量,其中有个操做是要修改这个变量。咱们把第一个操做先判断有木有改变,而后故意延迟一下,这个时候咱们看下输出结果:

2015-01-03 15:42:21.351 测试[1652:60015] --test--3-
2015-01-03 15:42:21.351 测试[1652:60013] --modify--4-
2015-01-03 15:42:21.351 测试[1652:60014] --test--1-
2015-01-03 15:42:22.355 测试[1652:60014] ====changed===

咱们能够看到,再次判断的时候,已经被修改了,若是咱们在实际的业务中这样去判断某些关键性的变量,可能就会出现严重的问题。下面看看咱们如何使用dispatch_barrier_async来进行同步:

//并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--5-", strTest);
    });

如今看下输出结果:

2015-01-03 16:00:27.552 测试[1786:65947] --test--1-
2015-01-03 16:00:27.552 测试[1786:65965] --test--3-
2015-01-03 16:00:29.553 测试[1786:65947] --test--2-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--4-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--5-

如今咱们能够发现操做4用dispatch_barrier_async加入操做后,前面的操做3以前都操做完成以前这个strTest都没有变。然后面的操做都是改变后的值。这样咱们的数据冲突的问题就解决了。
如今说明下这个函数干的事情,当这个函数加入到队列后,里面block并非当即执行的,它会先等待以前正在执行的block所有完成后,才执行,而且在它以后加入到队列中的block也在它操做结束后才能恢复以前的并发执行。咱们能够把这个函数理解为一条分割线,以前的操做,以后加入的操做。还有一个点要说明的是这个queue必须是用dispatch_queue_create建立出来的才行。

使用Dispatch Semaphore

dispatch_semaphore_t 相似信号量,能够用来控制访问某一资源访问数量。
使用过程:

  1. 先建立一个Dispatch Semaphore对象,用整数值表示资源的可用数量
  2. 在每一个任务中,调用dispatch_semaphore_wait来等待
  3. 得到资源就能够进行操做
  4. 操做完后调用dispatch_semaphore_signal来释放资源
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--3-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--5-", strTest);
        dispatch_semaphore_signal(semaphore);
    });

这样咱们同样能够保证,线程的数据安全。

相关文章
相关标签/搜索