ios多线程之GCD

多线程相关概念

  • 进程:可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
  • 线程:组成进程的基本执行单元,一个进程由多个线程组成。
  • 主线程:更新UI的操作都在主线程处理,为的是保证线程安全。假如两个子线程同时去同时设置控件图片就有可能由于这个图片被释放两次,造成程序崩溃。主线程一般不执行耗时操作,保证界面不卡顿,提高用户体验。
  • 子线程 :除了主线程以外的线程。可在后台执行耗时操作,如网络请求,逻辑数据处理等。
  • NSRunLoop:点击这里。

GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

  • dispatch_queue:执行任务的队列,遵循FIFO的顺序执行处理任务。有两种Dispatch Queue:Serial Dispatch QueueConcurrent Dispatch Queue。前者等待现在执行中的任务结束再执行下一个,使用了一个线程;后者不等待现在执行中的任务结束,使用了多个线程。
  • dispatch_queue_create:一种能够生成Dispatch Queue的API。dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)label参数指Dispatch Queue的名字,attr参数可以是指哪种Dispatch Queue。
  • dispatch_get_global_queue:另一种能够生成Dispatch Queue的API。该API生成的Dispatch Queue是所有应用程序都能够使用的。dispatch_get_global_queue(long identifier, unsigned long flags) identifier是执行优先级的标识符,可取值为以下之一:DISPATCH_QUEUE_PRIORITY_HIGH,、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND,分别代表着优先级高、默认、低、后台。flags参数保留在未来使用。一般取0,如果非0可能返回NULL。(flags参数文档也是这么说的)。
  • dispatch_async:异步执行队列。void dispatch_async(dispatch_queue_t queue, dispatch_block_t block),queue指要执行的队列,block是要 执行的任务。
  • dispatch_asyncvoid dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block),queue指要执行的队列,block是要 执行的任务。

代码示例

dispatch_queue_t serial_quque = dispatch_queue_create("test_serial_queue", NULL);//相当于DISPATCH_QUEUE_SERIAL
    dispatch_queue_t concurrent_queue = dispatch_queue_create("test_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(serial_quque, ^{
        NSLog(@"task1 in serial_queue");
        [NSThread sleepForTimeInterval:10];//休眠个10秒
    });

    dispatch_async(serial_quque, ^{
        NSLog(@"task2 in serial_quque");
    });

    dispatch_async(concurrent_queue, ^{
        NSLog(@"task1 in concurrent_queue");
        [NSThread sleepForTimeInterval:10];//休眠个10秒
    });

    dispatch_async(concurrent_queue, ^{
        NSLog(@"task2 in concurrent_queue");
    });

运行结果:

2018-09-05 16:43:40.075903+0800 Test[12213:1549611] task1 in serial_queue
2018-09-05 16:43:40.076031+0800 Test[12213:1549613] task1 in concurrent_queue
2018-09-05 16:43:40.076433+0800 Test[12213:1549612] task2 in concurrent_queue
2018-09-05 16:43:50.080947+0800 Test[12213:1549611] task2 in serial_quque

task2 in serial_quque正好比task1 in serial_queue晚执行了10s,说明是同一个线程;
task1 in concurrent_queue和task1 in concurrent_queue可以认为几乎同时执行,说明是两个线程。

dispatch_set_target_queue

可以变更生成的Dispatch Queue的执行优先级。void dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue)要变更优先级的Dispatch Queue,要使用相同优先级的Dispatch Queue。另外,dispatch_set_target_queue不仅可以变成优先级,还可以做成Dispatch Queue的执行阶层。比如在多个Serial Dispatch Queue中用dispatch_set_target_queue指定目标为某个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
代码示例

dispatch_queue_t serial_quque1 = dispatch_queue_create("test_serial_queue1", NULL);
dispatch_queue_t serial_quque2 = dispatch_queue_create("test_serial_queue2", NULL);
dispatch_queue_t serial_quque3 = dispatch_queue_create("test_serial_queue3", NULL);
dispatch_queue_t serial_quque10 = dispatch_queue_create("test_serial_queue10", NULL);


//    dispatch_set_target_queue(serial_quque1, serial_quque10);
//    dispatch_set_target_queue(serial_quque2, serial_quque10);
//    dispatch_set_target_queue(serial_quque3, serial_quque10);

dispatch_async(serial_quque10, ^{
    dispatch_async(serial_quque1, ^{
        NSLog(@"-----serial_quque1");
        NSThread *currentThread = [NSThread currentThread];
        NSLog(@"-----thread1:%p",currentThread);
        [NSThread sleepForTimeInterval:3];
    });

    dispatch_async(serial_quque2, ^{
        NSLog(@"-----serial_quque2");
        NSThread *currentThread = [NSThread currentThread];
        NSLog(@"-----thread2:%p",currentThread);
        [NSThread sleepForTimeInterval:3];
    });

    dispatch_async(serial_quque3, ^{
        NSLog(@"-----serial_quque3");
        NSThread *currentThread = [NSThread currentThread];
        NSLog(@"-----thread3:%p",currentThread);
        [NSThread sleepForTimeInterval:3];
    });


});

运行结果:

2018-09-06 10:18:12.967948+0800 Test[12755:1620521] -----serial_quque1
2018-09-06 10:18:12.968003+0800 Test[12755:1620521] -----thread1:0x1c0271640
2018-09-06 10:18:12.968048+0800 Test[12755:1620522] -----serial_quque2
2018-09-06 10:18:12.968092+0800 Test[12755:1620520] -----serial_quque3
2018-09-06 10:18:12.968113+0800 Test[12755:1620520] -----thread3:0x1c02721c0
2018-09-06 10:18:12.968133+0800 Test[12755:1620522] -----thread2:0x1c4667400

结果显示分别开了三个线程,并行执行,我们再把上面的注释去掉,运行一遍,结果如下:

2018-09-06 10:18:44.578115+0800 Test[12759:1620999] -----serial_quque1
2018-09-06 10:18:44.578168+0800 Test[12759:1620999] -----thread1:0x1c0266340
2018-09-06 10:18:47.583390+0800 Test[12759:1620999] -----serial_quque2
2018-09-06 10:18:47.583461+0800 Test[12759:1620999] -----thread2:0x1c0266340
2018-09-06 10:18:50.588539+0800 Test[12759:1620999] -----serial_quque3
2018-09-06 10:18:50.588696+0800 Test[12759:1620999] -----thread3:0x1c0266340

可见,同时只能执行一个处理,变成串行处理了。
所以,在必须将不可执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并行执行。

dispatch_after

可以实现延时执行处理。可以将指定的block追加到对应的Dispatch Queue中,主要代码如下:

dispatch_queue_t serial_quque1 = dispatch_queue_create("test_serial_queue1", NULL);
dispatch_time_t time= dispatch_time(DISPATCH_TIME_NOW, 2ull *NSEC_PER_SEC);
dispatch_after(time, serial_quque1, ^{
    /*
     *执行处理
     */
});

要注意的是,如果追加到Main Dispatch Queue在主线程的RunLoop中执行的话,在每隔1/60秒执行的RunLoop中,上述代码Block最快在2秒后执行处理,最慢在2+1/60秒后执行,并且Main Dispatch Queue有大量处理追加或主线程的处理,本身就有延迟,这个时间会更长。

dispatch_sync

dispatch_async是将指定的block非同步(异步)地追加到指定的Dispatch Queue中,dispatch_async不做任何等待就返回。而dispatch_sync就意着“同步”,在追加的block结束之前,dispatch_sync函数会一直在等待。
值得注意的是,dispatch_async容易造成死锁,例如在主线程执行以下代码就会发生死锁:

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"hello");
});

上述代码中dispatch_sync在等待返回结果,造成主线程在等待,而输出hello的block也是在主线程中执行,这个block也在等待主线程执行,从而造成死锁。
除了只在主线程造成死锁以外,两个线程也可以造成死锁:

dispatch_queue_t serial_quque = dispatch_queue_create("test_serial_queue", NULL);
dispatch_async(serial_quque, ^{
    int i = 0;
    while (i < 2) {
        i++;
        i--;
    }
});

NSLog(@"before hello");
dispatch_sync(serial_quque, ^{
    NSLog(@"hello");
});

NSLog(@"after hello");

会发现控制台只输出了一句before hello,就再也没输出了,serial_quque所对应的线程一直在执行dispatch_async追加的block,dispatch_sync追加的bock也都在等待该线程结束,而主线程就一直等待dispatch_sync返回结果,所以造成死锁。

Dispatch Group

当使用多个Serial Dispatch Queue或者使用concurrent Dispatch Queue并行执行多个处理时,我们知道,这些处理的顺序是不定的,所有处理都执行完的时间也是不定的,如果我们想在所有处理结束以后执行某个处理,这时就可以用Dispatch Group。
代码示例

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

dispatch_group_async(group, queue, ^{
    NSLog(@"block1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"block2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"block3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All is done.");
});

使用dispatch_group_create函数生成一个Dispatch Group,使用dispatch_group_notify函数在group的所有处理都执行完后通知某个线程执行某个处理。与dispatch_async追加的block属于queue不同的是,dispatch_group_async追加的block是属于group的。
另外,还可以用dispatch_group_wait来指定等待的时间来观察group是否已经执行完:

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

dispatch_group_async(group, queue, ^{
    NSLog(@"block1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"block2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"block3");
});


long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC));
if (result == 0) {
    //group已经执行完所有的处理
}else{
    //group还未执行完所有的处理
}

dispatch_group_wait第二个参数指定等待的时间,当使用DISPATCH_TIME_FOREVER时,dispatch_group_wait会在当前线程一直等到到group执行完了才有返回结果,并且返回结果恒为0。当dispatch_group_wait返回值为0时代表group已经执行完所有处理,非0时则未执行完所有处理。
值得注意的是 ,dispatch_group_wait会造成调用该函数的线程(当前线程)停止,等待dispatch_group_wait返回结果,存在死锁的发生,所以还是推荐使用dispatch_group_notify

dispatch_barrier_async

dispatch_barrier_async起到了一种在并行的多线程处理中再插入隔离的操作,该隔离操作是在插入之前的并行操作执行完以后再执行,当该隔离操作执行完了以后后续的并行处理可以继续并行执行。
比如文件或者数据的读和写,对于读操作,可以并行处理,毕竟内容都一样,但如果在读的同时要执行写操作的话,就会造成读取的数据不符或数据竞争,所以应该使用dispatch_barrier_async进行写操作:

dispatch_queue_t quque = dispatch_queue_create("test_serial_queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block1_reading);
dispatch_async(quque, block2_reading);
dispatch_async(quque, block3_reading);
dispatch_async(quque, block4_reading);

dispatch_barrier_async(quque, block_writing);

dispatch_async(queue, block5_reading);
dispatch_async(quque, block6_reading);
dispatch_async(quque, block7_reading);
dispatch_async(quque, block8_reading);

所以,通过dispatch_barrier_async隔离开写操作,保证数据安全。

Dispatch Semaphore

Dispatch Semaphore也可以实现并行处理的线程等待,主要通过信号量来控制,相关API的说明有点绕,需要仔细分析。
1. dispatch_semaphore_create函数可以创建一个信号量,只有一个参数value,一般传一个0就可以在两个线程里调度,如果需要管理有限的资源池,那么资源的数量就是value的值。
2. dispatch_semaphore_signal函数:参数是一个dispatch_semaphore_t类型(关联的信号量),调用该函数会使得信号量加1。
3. dispatch_semaphore_wait函数:第一个参数是dispatch_semaphore_t类型(关联的信号量);第二个参数是dispatch_time_t类型,该参数表示当前线程等待信号量的时间。调用该函数会使信号量减1,如果信号量小于0,则会在返回结果之前使当前线程一直处于等待状态,那么何时返回结果呢?就是经过了由第二参数timeout定义的时间。一旦第二参数取值DISPATCH_TIME_FOREVER,就意味着当前线程一直等待(阻塞线程),等到信号量大于或等于0才会唤醒。
代码示例

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

以上代码会导致当前线程一直等到,因为信号量在dispatch_semaphore_wait以后一直都是小于0。
再来:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(dispatch_queue_create("test_queue1", NULL), ^{

    NSLog(@"in test queue1");
    NSLog(@"sleep for 2s in test queue1");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"sleeping finished in test queue1");
    dispatch_semaphore_signal(semaphore);//信号量加1
});

dispatch_async(dispatch_queue_create("test_queue2", NULL), ^{

    NSLog(@"in test queue2");
    NSLog(@"sleep for 3s in test queue2");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"sleeping finished in test queue2");
    dispatch_semaphore_signal(semaphore);//信号量加1
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//信号量减1,如果减1后还是小于0,就继续等待信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"waiting finished");

运行结果:

2018-09-07 09:51:58.971196+0800 Test[4268:862931] in test queue1
2018-09-07 09:51:58.971199+0800 Test[4268:862930] in test queue2
2018-09-07 09:51:58.971248+0800 Test[4268:862931] sleep for 2s in test queue1
2018-09-07 09:51:58.971263+0800 Test[4268:862930] sleep for 3s in test queue2
2018-09-07 09:52:00.971804+0800 Test[4268:862931] sleeping finished in test queue1
2018-09-07 09:52:01.976477+0800 Test[4268:862930] sleeping finished in test queue2
2018-09-07 09:52:01.976733+0800 Test[4268:862851] waiting finished

可见,当前线程还是会等待test_queue2对应的线程休眠完3秒才会结束等待。
虽然Dispatch Group和dispatch_barrier_async也可以实现多线程调度和通信,但Dispatch Semaphore却可以实现粒度更小的排他控制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值