c语言1ull,15583995000970.html

GCD备览 - 鸟的博客

4666628.png鸟的博客

2018.11-2019.10期间收集、整合的iOS开发进阶资料,仅供学习参考之用,谢绝传播及他用

GCD备览

2019/5/21

1. 队列Dispatch Queue

1.1 Dispatch Queue

GCD中有两种队列:Serial Dispatch Queue串行队列和Concurrent Dispatch Queue并行队列。串行队列同时只能执行一个追加处理,并行队列可以同时执行多个追加的处理。系统对于一个串行队列就只生成并使用一个线程。对于并行队列,XNU内核只使用有效管理的线程。

串行队列又有主队列和自定义串行队列两种。并行队列包括全局队列和自定义并行队列两种。

1.2 队列的创建、获取

串行队列
1、主队列的获取:

dispatch_get_main_queue()

2、自定义队列(串行)的创建:(参数为优先级、第二个参数DISPATCH_QUEUE_SERIAL实际上是指向'NULL'的宏)

dispatch_queue_create("queue_name", DISPATCH_QUEUE_SERIAL)

并行队列
1、全局队列的获取:(参数为优先级、第二个参数为0)

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

2、自定义队列(并行)的创建:

dispatch_queue_create("ss_queue", DISPATCH_QUEUE_CONCURRENT)

可以看出来、自定义队列都是使用dispatch_queue_create('队列名','队列类型')进行创建。队列名称建议使用应用程序ID这种逆序全程域名。

1.3 Main dispatch Queue/Global Dispatch Queue

主分发队列是在主线程中执行的Dispatch Queue,因为主线程只有一个,所以它是串行队列。追加到主分发队列的处理在主线程的RunLoop中执行。由于在主线程中执行,因此要将用户界面更新等一些必须在主线程中执行的处理追加到主分发队列使用。

全局分发队列是所有应用程序都能使用的并发队列。有4个执行优先级,High Priority、Default Priority、Low Priority、Background Priority。通过XNU内核管理的用于全局分发队列的线程,将各自使用的全局分发队列的执行优先级作为线程的执行优先级使用。在向全局分发队列追加任务时,应选择与处理内容对应的执行优先级的全局分发队列。但是通过XNU内核用于全局分发队列的线程并不能保证实时性。

1.4 向队列添加任务

我们可以使用dispatch_async或者dispatch_async_f函数异步的向队列中添加任务,也就是说当我们添加完任务后该函数会立即返回,我们不需要等待任务执行完成,而且我们也不会知道队列到底何时开始执行任务。dispatch_async函数有两个参数,一个是目标队列,类型为dispatch_queue_t,另一个是闭包,类型为dispatch_block_t:

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MySerialQueue", nil) dispatch_async(serialQueue, {

NSLog(@"Task in the queue...")

});

dispatch_async_f函数有三个参数,第一个是类型为dispatch_queue_t的目标队列,第二个是队列上下文指针,第三个是类型为dispatch_function_t的任务函数,队列上下文指针为该函数的唯一参数:

- (void)viewDidLoad {

[super viewDidLoad];

int context = 10;

dispatch_async_f(dispatch_get_global_queue(0, 0), &context, test);

}

void test(void*context) {

int *c = context;

NSLog(@"%d", *c);

}

GCD中有一些以_f结尾的函数,表明参数是函数而不是block。

除了这两个函数,我们还可以使用dispatch_sync和dispatch_sync_f函数同步的向队列中添加任务,并且我们要等待直到任务执行完成。这两个函数和上面的异步添加任务函数用法完全一致。

那么什么时候用异步什么时候用同步呢,大多数情况下我们都是在主线程中使用GCD分派任务,为了避免阻塞主线程,影响用户体验,所以通常情况下我们都使用异步添加任务的方式。当然为了避免任务与主线程中产生资源竞争的问题,有时候酌情也会使用同步添加任务的方式。

1.5 dispatch_set_target_queue

dispatch_queue_create函数生成的队列都使用与默认优先级的全局分发队列相同执行优先级的线程。而变更生成的队列的执行优先级要使用dispatch_set_target_queue函数。在后台执行动作处理的串行分发队列的生成方法如下:

dispatch_queue_t mySerialDispatchQueue =

dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);

dispatch_queue_t globalDispatchQueueBackground =

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

dispatch_set_target_queue不仅可以更改队列执行优先级,还可以作成队列的执行阶层。如果在多个串行分发队列中使用dispatch_set_target_queue函数指定目标为某一个串行分发队列,那么原先本应并行执行的多个串行分发队列,在目标串行队列分发队列上只能同时执行一个处理。
举个例子:

+(void)testTargetQueue {

dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);

dispatch_set_target_queue(queue1, targetQueue);

dispatch_set_target_queue(queue2, targetQueue);

dispatch_set_target_queue(queue3, targetQueue);

dispatch_async(queue1, ^{

NSLog(@"1 in");

[NSThread sleepForTimeInterval:3.f];

NSLog(@"1 out");

});

dispatch_async(queue2, ^{

NSLog(@"2 in");

[NSThread sleepForTimeInterval:2.f];

NSLog(@"2 out");

});

dispatch_async(queue3, ^{

NSLog(@"3 in");

[NSThread sleepForTimeInterval:1.f];

NSLog(@"3 out");

});

}

打印结果:

2014-11-13 13:18:05.964 TUPIAN[2701:92951] 1 in

2014-11-13 13:18:08.965 TUPIAN[2701:92951] 1 out

2014-11-13 13:18:08.966 TUPIAN[2701:92951] 2 in

2014-11-13 13:18:10.967 TUPIAN[2701:92951] 2 out

2014-11-13 13:18:10.968 TUPIAN[2701:92951] 3 in

2014-11-13 13:18:11.969 TUPIAN[2701:92951] 3 out

总结:dispatch_set_target_queue可以设置queue的优先级,也可以使多个serial queue在目标queue上一次只有一个执行。

1.6 线程的开辟与阻塞机制

  • 并行和串行主要影响:任务的执行方式
    并行:多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务
  • 同步和异步主要影响:能不能开启新的线程
    同步:在当前线程中执行任务,不具备开启新线程的能力
    异步:在新的线程中执行任务,具备开启新线程的能力

会开辟新线程的两种情况:
并行队列+异步任务 = 多条新线程
自定义串行队列+异步任务 = 一条新线程

其余情况、全部将会置于当前线程/主线程(主队列任务)下执行。

用我们常用的代码举个例子:

dispatch_queue_t q = dispatch_queue_create("test.q", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(q, ^{

for (int i = 0; i < 100000; i ++) {

NSLog(@"2--%d----%@",i,[NSThread currentThread]);

sleep(3);

}

});

dispatch_async(q, ^{

for (int i = 0; i < 100000; i ++) {

NSLog(@"1---%d----%@",i,[NSThread currentThread]);

sleep(3);

}

});

打印结果:

2018-03-19 15:32:12.591483+0800 test[5002:479872] 1---0----<NSThread: 0x600000278140>{number = 4, name = (null)}

2018-03-19 15:32:12.591549+0800 test[5002:479875] 2--0----<NSThread: 0x60400027b240>{number = 3, name = (null)}

2018-03-19 15:32:15.595894+0800 test[5002:479872] 1---1----<NSThread: 0x600000278140>{number = 4, name = (null)}

2018-03-19 15:32:15.595894+0800 test[5002:479875] 2--1----<NSThread: 0x60400027b240>{number = 3, name = (null)}

2018-03-19 15:32:18.601144+0800 test[5002:479872] 1---2----<NSThread: 0x600000278140>{number = 4, name = (null)}

2018-03-19 15:32:18.601144+0800 test[5002:479875] 2--2----<NSThread: 0x60400027b240>{number = 3, name = (null)}

这就是上面所说的并行队列+异步任务 = 多条新线程。

如果我们把DISPATCH_QUEUE_CONCURRENT换成DISPATCH_QUEUE_SERIAL
打印结果又会变成

2018-03-19 15:40:20.491705+0800 test[5083:493760] 2--0----<NSThread: 0x600000278d00>{number = 3, name = (null)}

2018-03-19 15:40:23.493296+0800 test[5083:493760] 2--1----<NSThread: 0x600000278d00>{number = 3, name = (null)}

2018-03-19 15:40:26.497652+0800 test[5083:493760] 2--2----<NSThread: 0x600000278d00>{number = 3, name = (null)}

所有的输出都集中在number = 3的新线程中。

1.7 ARC兼容性

对于dispatch_queue_t
对象来说,我们应该这么写

#if OS_OBJECT_USE_OBJC

@property (strong, nonatomic) dispatch_queue_t barrierQueue;#else

@property (assign, nonatomic)

dispatch_queue_t barrierQueue;

#endif

在dealloc方法中应该加上

#if !OS_OBJECT_USE_OBJC   //这个宏是在sdk6.0之后才有的,如果是之前的,则OS_OBJECT_USE_OBJC为0

dispatch_release(_barrierQueue);

#endif

当然了,也可以使用

#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000 //

6.0sdk之前

dispatch_release(_barrierQueue);

#endif

这里的宏 __IPHONE_OS_VERSION_MIN_REQUIRED 就是我们在工程的设置项里设置的最低部署sdk版本。

原因就是对于最低sdk版本>=ios6.0来说,GCD对象已经纳入了ARC的管理范围,我们就不需要再手工调用 dispatch_release了,否则的话,在sdk<6.0的时候,即使我们开启了ARC,这个宏OS_OBJECT_USE_OBJC
也是没有的,也就是说这个时候,GCD对象还必须得自己管理

如果你部署的最低目标低于 iOS 6.0 or Mac OS X 10.8
你应该自己管理GCD对象,使用(dispatch_retain,dispatch_release),ARC并不会去管理它们
如果你部署的最低目标是 iOS 6.0 or Mac OS X 10.8 或者更高的
ARC已经能够管理GCD对象了,这时候,GCD对象就如同普通的OC对象一样,不应该使用dispatch_retain和dispatch_release。

思考:异步追加任务到并行队列后,立即释放队列是否会产生问题?
不会,在dispatch_asyc函数中追加Block到Dispatch Queue,该Block通过dispatch_retain函数持有Dispatch Queue。一旦Block执行结束,就通过dispatch_release函数释放该Block持有的Dispatch Queue。

1.8 Dispatch Queue的实现

Dispatch Queue的实现需要:

  • 用于管理追加的Block的C语言层实现的FIFO队列
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用户管理线程的C语言层实现的一些容器

实现Dispatch Queue使用的软件组件:
libdispatch:提供Dispatch Queue
Libc(pthreads):pthread_workqueue
XNU内核:workqueue

GCD的API全部包含在libdispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列是管理通过dispatch_async等函数所追加的Block。
Block并不是直接加入FIFO队列中,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后再加入FIFO队列。该Dispatch Continuation用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文。
Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue。但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的Global Dispatch Queue。

Main Dispatch Queue在RunLoop中执行Block。
Global Dispatch Queue有8种优先级。
High Priority,Default Priority,Low Priority,Background Priority,High Overcommit Priority,Default Overcommit Priority,Low Overcommit Priority,Background Overcommit Priority。
优先级中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。如Overcommit这个名字所示,不管系统状态如何,这些Dispatch Queue都会强制生成线程。
这8种Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue。
pthread_workqueue包含在Libc提供的pthread API中。其使用bsdthread_register和workq_open系统调用,在初始化XNU内核的workqueue之后获取workqueue信息。
XNU内核持有4种workqueue:

  • WORKQUEUE_HIGH_PRIOQUEUE
  • WORKQUEUE_DEFAULT_PRIOQUEUE
  • WORKQUEUE_LOW_PRIOQUEUE
  • WORKQUEUE_BG_PRIOQUEUE
    以上为4种执行优先级的workqueue。该执行优先级与Global Dispatch Queue的4种执行优先级相同。

下面看一下Dispatch Queue中执行Block的过程。当在Global Dispatch Queue中执行Block时,libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。
pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的Global Dispatch Queue,workqueue则始终生成线程。
另外,因为workqueue生成的线程在实现用于workqueue的线程计划表中运行,所以与一般线程的上下文切换不同。这里也隐藏着使用GCD的原因。
workqueue的线程执行pthread_workqueueh函数,该函数调用libdispatch的回调函数。在该回调函数中执行加入到Dispatch Continuation的Block。
Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理,开始准备执行加入到Global Dispatch Queue中的下一个Block。
以上就是Dispatch queue执行的大概过程。

2. 延时追加任务dispatch_after

dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到分发队列。dispatch_after与延时若干秒后用dispatch_async函数追加Block到队列的效果相同。
因为主队列在主线程的RunLoop中执行,所以在比如间隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在主队列有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
dispatch_after的第一个参数是dispatch_time_t类型的值。该值使用dispatch_time函数或dispatch_walltime函数生成。dispatch_time用于计算相对时间,而dispatch_walltime用于计算绝对时间。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC)

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,150ull*NSEC_PER_MSEC)

NSEC_PER_SEC是毫微妙,NSEC_PER_MSEC是毫秒。

3. 队列组dispatch_group

dispatch_group_notify

在追加到某个队列中的多个任务结束后想执行结束处理,这种情况下适合使用队列组。通过dispatch_group_async向队列组追加任务,使用dispatch_group_notify在合适的队列里追加结束处理。
测试例子:

//队列组

- (void)dispatch_group_test {

dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{

NSLog(@"任务1——准备休眠3秒");

sleep(3);

NSLog(@"任务1——完成");

});

NSLog(@"主线程——准备休眠5秒");

sleep(5);

NSLog(@"主线休眠结束");

dispatch_group_async(group, queue, ^{

NSLog(@"任务2——准备休眠10秒");

sleep(10);

NSLog(@"任务2——完成");

});

dispatch_group_notify(group, queue, ^{

NSLog(@"任务组完成");

});

NSLog(@"主线程结束");

}

打印结果:

2018-03-20 10:43:32.135642+0800 test[1763:94602] 主线程——准备休眠5秒

2018-03-20 10:43:32.135649+0800 test[1763:94645] 任务1——准备休眠3秒

2018-03-20 10:43:35.141269+0800 test[1763:94645] 任务1——完成

2018-03-20 10:43:37.137225+0800 test[1763:94602] 主线休眠结束

2018-03-20 10:43:37.137513+0800 test[1763:94602] 主线程结束

2018-03-20 10:43:37.137515+0800 test[1763:94645] 任务2——准备休眠10秒

2018-03-20 10:43:47.139737+0800 test[1763:94645] 任务2——完成

2018-03-20 10:43:47.140116+0800 test[1763:94645] 任务组完成

dispatch_group_wait

相比于dispatch_group_notify是异步处理的,也可以使用dispatch_group_wait函数进行同步等待,该函数会等待全部处理执行结束。
使用例子:

//队列组

- (void)dispatch_group_test {

dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{

NSLog(@"任务1——准备休眠3秒");

sleep(3);

NSLog(@"任务1——完成");

});

NSLog(@"主线程——准备休眠5秒");

sleep(5);

NSLog(@"主线休眠结束");

dispatch_group_async(group, queue, ^{

NSLog(@"任务2——准备休眠10秒");

sleep(10);

NSLog(@"任务2——完成");

});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"主线程结束");

}

dispatch_group_wait函数的返回值不为0表示虽然经过了指定的时间,但属于分发组的某一任务还在执行中。如果返回0,那么全部处理执行结束。指定DISPATCH_TIME_FOREVER表示会等待到所有任务执行结束,指定DISPATCH_TIME_NOW则不用任何等待即可判定属于分发组的处理是否执行结束。

long result = dispatch_group_wait(group,DISPATCH_TIME_NOW)

dispatch_group_enter和dispatch_group_leave

dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。

dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。

这两种通知可以在多线程间自由穿梭的。用于任务计数管理,可用于解决dispatch_group_async内追加异步任务时执行提前结束的问题。

dispatch_queue_t queue = dispatch_queue_create("dispatchGroupMethod1.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{

dispatch_async(queue, ^{

for (NSInteger i =0; i<3; i++) {

sleep(1);

NSLog(@"任务1-异步任务执行-:%ld,thread:%@",(long)i,[NSThread currentThread]);

}

});

});

dispatch_group_async(group, queue, ^{

dispatch_async(queue, ^{

for (NSInteger i =0; i<3; i++) {

sleep(1);

NSLog(@"任务2-异步任务执行-:%ld,thread:%@",(long)i,[NSThread currentThread]);

}

});

});

//等待上面的任务全部完成后,会收到通知执行block中的代码 (不会阻塞线程)

dispatch_group_notify(group, queue, ^{

NSLog(@"全部任务执行完成");

});

打印结果:

2018-03-09 16:54:37.009913+0800 ceshi[3934:349413] 全部任务执行完成

2018-03-09 16:54:38.014204+0800 ceshi[3934:349411] 任务1-异步任务执行-:0,thread:<NSThread: 0x60000026cb00>{number = 3, name = (null)}

2018-03-09 16:54:38.014204+0800 ceshi[3934:349414] 任务2-异步任务执行-:0,thread:<NSThread: 0x6040004799c0>{number = 4, name = (null)}

2018-03-09 16:54:39.018626+0800 ceshi[3934:349414] 任务2-异步任务执行-:1,thread:<NSThread: 0x6040004799c0>{number = 4, name = (null)}

2018-03-09 16:54:39.018626+0800 ceshi[3934:349411] 任务1-异步任务执行-:1,thread:<NSThread: 0x60000026cb00>{number = 3, name = (null)}

2018-03-09 16:54:40.020653+0800 ceshi[3934:349411] 任务1-异步任务执行-:2,thread:<NSThread: 0x60000026cb00>{number = 3, name = (null)}

2018-03-09 16:54:40.020653+0800 ceshi[3934:349414] 任务2-异步任务执行-:2,thread:<NSThread: 0x6040004799c0>{number = 4, name = (null)}

异步并发,dispatch_group_notify先执行,没在最后调用。
而使用dispatch_group_enter和dispatch_group_leave时:

//异步

dispatch_queue_t queue = dispatch_queue_create("dispatchGroupMethod1.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_async(queue, ^{

dispatch_group_enter(group);

for (NSInteger i =0; i<3; i++) {

sleep(1);

NSLog(@"任务1-异步任务执行-:%ld,thread:%@",(long)i,[NSThread currentThread]);

}

dispatch_group_leave(group);

});

dispatch_async(queue, ^{

dispatch_group_enter(group);

for (NSInteger i =0; i<3; i++) {

sleep(1);

NSLog(@"任务2-异步任务执行-:%ld,thread:%@",(long)i,[NSThread currentThread]);

}

dispatch_group_leave(group);

});

//等待上面的任务全部完成后,会收到通知执行block中的代码 (不会阻塞线程)

dispatch_group_notify(group, queue, ^{

NSLog(@"全部任务执行完成");

});

打印结果:

//结果

2018-03-09 17:28:47.644739+0800 ceshi[4322:392667] 任务2-异步任务执行-:0,thread:<NSThread: 0x600000266b00>{number = 4, name = (null)}

2018-03-09 17:28:47.644739+0800 ceshi[4322:392666] 任务1-异步任务执行-:0,thread:<NSThread: 0x60400027dbc0>{number = 3, name = (null)}

2018-03-09 17:28:48.646245+0800 ceshi[4322:392667] 任务2-异步任务执行-:1,thread:<NSThread: 0x600000266b00>{number = 4, name = (null)}

2018-03-09 17:28:48.646245+0800 ceshi[4322:392666] 任务1-异步任务执行-:1,thread:<NSThread: 0x60400027dbc0>{number = 3, name = (null)}

2018-03-09 17:28:49.650996+0800 ceshi[4322:392667] 任务2-异步任务执行-:2,thread:<NSThread: 0x600000266b00>{number = 4, name = (null)}

2018-03-09 17:28:49.650996+0800 ceshi[4322:392666] 任务1-异步任务执行-:2,thread:<NSThread: 0x60400027dbc0>{number = 3, name = (null)}

2018-03-09 17:28:49.651347+0800 ceshi[4322:392666] 全部任务执行完成

4. 线程栅栏dispatch_barrier

dispatch_barrier_async(dispatch_barrier_sync)会等待追加到并行队列上的并行执行的处理全部结束之后,再将指定的任务追加到该队列中。然后再由dispatch_barrier_sync函数追加的处理执行完毕后,队列才恢复为一般的动作,追加到该队列的处理又开始并行执行。
使用例子:

- (void)dispatch_barrier_test {

dispatch_queue_t queue = dispatch_queue_create("test_queue", DISPATCH_QUEUE_CONCURRENT);

for (int i = 1; i <= 3; i ++) {

dispatch_async(queue, ^{

sleep(3);

NSLog(@"任务%d结束",i);

});

}

NSLog(@"代码经过栅栏");

dispatch_barrier_sync(queue, ^{

sleep(5);

NSLog(@"栅栏结束");

});

NSLog(@"代码通过栅栏");

for (int i = 4; i <= 6; i ++) {

dispatch_async(queue, ^{

sleep(3);

NSLog(@"任务%d结束",i);

});

}

NSLog(@"代码结束");

}

打印结果:

018-03-20 16:44:07.347195+0800 test[4987:607391] 代码经过栅栏

2018-03-20 16:44:10.351954+0800 test[4987:607438] 任务1结束

2018-03-20 16:44:10.351954+0800 test[4987:607439] 任务3结束

2018-03-20 16:44:10.351984+0800 test[4987:607440] 任务2结束

2018-03-20 16:44:15.353557+0800 test[4987:607391] 栅栏结束

2018-03-20 16:44:15.353836+0800 test[4987:607391] 代码通过栅栏

2018-03-20 16:44:15.354086+0800 test[4987:607391] 代码结束

2018-03-20 16:44:18.357376+0800 test[4987:607441] 任务5结束

2018-03-20 16:44:18.357369+0800 test[4987:607440] 任务4结束

2018-03-20 16:44:18.357457+0800 test[4987:607438] 任务6结束

将同步栅栏dispatch_barrier_sync换成异步栅栏dispatch_barrier_async。

2018-03-20 16:45:52.798543+0800 test[5025:610657] 代码经过栅栏

2018-03-20 16:45:52.798700+0800 test[5025:610657] 代码通过栅栏

2018-03-20 16:45:52.798823+0800 test[5025:610657] 代码结束

2018-03-20 16:45:55.798703+0800 test[5025:610699] 任务1结束

2018-03-20 16:45:55.798722+0800 test[5025:610702] 任务3结束

2018-03-20 16:45:55.798725+0800 test[5025:610698] 任务2结束

2018-03-20 16:46:00.800958+0800 test[5025:610698] 栅栏结束

2018-03-20 16:46:03.805894+0800 test[5025:610701] 任务6结束

2018-03-20 16:46:03.805886+0800 test[5025:610698] 任务4结束

2018-03-20 16:46:03.805987+0800 test[5025:610702] 任务5结束

很明显的区别在于同步栅栏会阻塞之后普通代码的执行、异步栅栏则不会。
应用线程栅栏的特性、我们可以更好的做一些线程同步。某些情况下不需要写好几层任务组来同步任务。

5. 循环追加dispatch_apply

dispatch_apply函数是dispatch_sync和dispatch group的关联API。该函数按指定的次数将指定的Block追加到指定的队列中,并等待全部处理执行结束。
使用例子:

- (void)dispatch_apply_test {

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

dispatch_apply(10, queue, ^(size_t index) {

NSLog(@"%@-%ld",[NSThread currentThread],index);

sleep(10);

});

NSLog(@"任务完成");

});

NSLog(@"主线程结束,Done");

}

打印结果:

2018-03-20 13:27:38.463507+0800 test[2491:271571] 主线程结束

2018-03-20 13:27:38.463630+0800 test[2491:271633] <NSThread: 0x60400026e180>{number = 3, name = (null)}-0

2018-03-20 13:27:38.463639+0800 test[2491:271928] <NSThread: 0x604000274680>{number = 4, name = (null)}-1

2018-03-20 13:27:38.463713+0800 test[2491:271932] <NSThread: 0x60000007ccc0>{number = 5, name = (null)}-2

2018-03-20 13:27:38.463715+0800 test[2491:271933] <NSThread: 0x60400026ecc0>{number = 6, name = (null)}-3

2018-03-20 13:27:48.468368+0800 test[2491:271932] <NSThread: 0x60000007ccc0>{number = 5, name = (null)}-6

2018-03-20 13:27:48.468368+0800 test[2491:271928] <NSThread: 0x604000274680>{number = 4, name = (null)}-4

2018-03-20 13:27:48.468382+0800 test[2491:271933] <NSThread: 0x60400026ecc0>{number = 6, name = (null)}-7

2018-03-20 13:27:48.468392+0800 test[2491:271633] <NSThread: 0x60400026e180>{number = 3, name = (null)}-5

2018-03-20 13:27:58.471068+0800 test[2491:271932] <NSThread: 0x60000007ccc0>{number = 5, name = (null)}-9

2018-03-20 13:27:58.471068+0800 test[2491:271933] <NSThread: 0x60400026ecc0>{number = 6, name = (null)}-8

2018-03-20 13:28:08.473167+0800 test[2491:271633] 任务完成

在全局并发队列中执行处理时,各个处理的执行时间不定,但是最后的Done必定在最后的位置。这是因为dispatch_apply会等待全部处理执行结束。
另外,由于dispatch_apply函数也和dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

6. 队列挂起与恢复dispatch_suspend/dispatch_resume

dispatch_suspend函数可用于挂起指定队列,dispatch_resume函数可恢复指定队列。这些函数对已经执行的处理没有影响。挂起后,追加到队列中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

7. 信号量Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。
通过dispatch_semaphore_create函数来生成Dispatch Semaphore。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

参数表示计数的初始值。

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大于或等于0。当计数值大于等于0,或者在待机中计数值大于等于0时,对该计数进行减1并从dispatch_semaphore_wait中返回。计数值等于0的时候,对该计数值减1变-1后,不立即返回,而是等待到计数值大于等于0才返回。超时也会返回,返回值不是0。第二个参数与dispatch_group_wait函数等相同,本例中表示永久等待。dispatch_semaphore_wait返回值和dispatch_group_wait相同。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);

long result = dispatch_semaphore_wait(semaphore, time);

if(result == 0){

//由于Dispatch Semaphore的计数值达到大于等于0,或者在待机中的指定时间内Dispatch Semaphore的计数值达到大于等于0,所以Dispatch Semaphore的计数值减去1.

//可执行需要进行排他控制的处理。

}else{

//由于Dispatch Semaphore的计数值为0,因此在达到指定时间为止待机。

}

dispatch_semaphore_wait函数返回0时,可安全地执行需要进行排他控制的处理。通过dispatch_semaphore_signal函数可以将Dispatch Semaphore的计数值加1.

GCD的信号量应用场景: 控制最大并发量, 控制资源的同步访问,如数据访问,网络同步加载.

8. 一次执行dispatch_once

dispatch_once函数保证在应用程序中只执行一次指定处理,常用在单例模式。
例子:

static id obj = nil;

+ (NSObject *)shareInstance

{

static NSObject* obj;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

obj = [[NSObject alloc]init];

});

return obj;

}

原理
dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
当onceToken= 0时,线程执行dispatch_once的block中代码
当onceToken= -1时,线程跳过dispatch_once的block中代码不执行
当onceToken为其他值时,线程被线程被阻塞,等待onceToken值改变

在上面的例子中,当线程首先调用shareInstance,某一线程要执行block中的代码时,首先需要改变onceToken的值,再去执行block中的代码。这里onceToken的值变为了140734605830464。
这样当其他线程再获取onceToken的值时,值已经变为140734605830464。其他线程被阻塞。
当block线程执行完block之后。onceToken变为-1。其他线程不再阻塞,跳过block。
下次再调用shareInstance时,block已经为-1。直接跳过block。
这样dispatch_once在首次调用时同步阻塞线程,生成单例之后,不再阻塞线程。dispatch_once是创建单例的最优方案。

结合具体例子:

- (NSObject *)getObj

{

static NSObject* obj;

static dispatch_once_t onceToken;

NSLog(@"1::::%ld",onceToken);

dispatch_once(&onceToken, ^{

NSLog(@"2::::%ld",onceToken);

obj = [[NSObject alloc]init];

NSLog(@"3::::%ld",onceToken);

});

NSLog(@"4::::%ld",onceToken);

return obj;

}

打印结果:

2018-03-20 18:08:04.066961+0800 test[5861:729479] 1::::0

2018-03-20 18:08:04.067124+0800 test[5861:729479] 2::::768

2018-03-20 18:08:04.067256+0800 test[5861:729479] 3::::768

2018-03-20 18:08:04.067355+0800 test[5861:729479] 4::::-1

9. Dispatch I/O和Dispatch Data

在读取较大文件时,如果将文件分成合适大小并使用全局分发队列并行读取的话,应该会比一般读取速度快不少。现今的输入输出硬件已经可以做到一次使用多个线程更快地并行读取了。实现这一功能的就是Dispatch I/O和Dispatch Data。

dispatch_async(queue, ^{ /* 读取  0     ~ 8080  字节*/ });

dispatch_async(queue, ^{ /* 读取 8081 ~ 16383 字节*/ });

dispatch_async(queue, ^{ /* 读取 16384 ~ 24575 字节*/ });

dispatch_async(queue, ^{ /* 读取 24576 ~ 32767 字节*/ });

dispatch_async(queue, ^{ /* 读取 32768 ~ 40959 字节*/ });

dispatch_async(queue, ^{ /* 读取 40960 ~ 49191 字节*/ });

dispatch_async(queue, ^{ /* 读取 49192 ~ 57343 字节*/ });

dispatch_async(queue, ^{ /* 读取 57344 ~ 65535 字节*/ });

像上面这样,将文件分割为一块一块地进行读取处理。通过Dispatch I/O读写文件时,使用全局分发队列将1个文件按某个大小读写。分割读取的数据通过使用Dispatch Data可更为简单地进行结合和分割。

请看下面苹果中使用Dispatch I/O 和 Dispatch Data的例子。下面的代码摘自Apple System Log API里的源代码(地址:http://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c)

// 创建串行队列

pipe_q = dispatch_queue_create("PipeQ", NULL);

// 创建 Dispatch I/O

pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){

close(fd);

});

*out_fd = fdpair[1];

// 该函数设定一次读取的大小(分割大小)

dispatch_io_set_low_water(pipe_channel, SIZE_MAX);

//

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){

if (err == 0) // err等于0 说明读取无误

{

// 读取完“单个文件块”的大小

size_t len = dispatch_data_get_size(pipedata);

if (len > 0)

{

// 定义一个字节数组bytes

const char *bytes = NULL;

char *encoded;

dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);

encoded = asl_core_encode_buffer(bytes, len);

asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);

free(encoded);

_asl_send_message(NULL, merged_msg, -1, NULL);

asl_msg_release(merged_msg);

dispatch_release(md);

}

}

if (done)

{

dispatch_semaphore_signal(sem);

dispatch_release(pipe_channel);

dispatch_release(pipe_q);

}

});

代码简单介绍:创建一个调度I / O通道。
1.创建一个调度I/O通道,并将其与指定的文件描述符关联,并指定发生error时用来执行处理的block,以及执行该block的Dispatch Queue。

    dispatch_io_t dispatch_io_create( dispatch_io_type_t type, dispatch_fd_t fd, dispatch_queue_t queue, void (^cleanup_handler)(int error));

参数:
type  通道类型 (Dispatch I/O Channel Types.)
#define DISPATCH_IO_STREAM 0
读写操作按顺序依次顺序进行。在读或写开始时,操作总是在文件指针位置读或写数据。读和写操作可以在同一个信道上同时进行。
#define DISPATCH_IO_RANDOM 1
随机访问文件。读和写操作可以同时执行这种类型的通道,文件描述符必须是可寻址的。

2.创建一个具有关联路径名的调度I / O通道。

dispatch_io_t dispatch_io_create_with_path( dispatch_io_type_t type, const char* path, int oflag, mode_t mode, dispatch_queue_t queue, void (^cleanup_handler)(int error));

3.从现有的信道创建一个新的调度I / O信道。

dispatch_io_t dispatch_io_create_with_io( dispatch_io_type_t type, dispatch_io_t io, dispatch_queue_t queue, void (^cleanup_handler)(int error));

读写操作
1.在指定的信道上调度异步读操作。

void dispatch_io_read( dispatch_io_t channel, off_t offset, size_t length, dispatch_queue_t queue, dispatch_io_handler_t io_handler);

dispatch_io_read 函数使用Global Dispatch Queue 开始并发读取。每当各个分割的文件块读取结束时,将含有文件块数据的 Dispatch Data(这里指pipedata) 传递给 “dispatch_io_read 函数指定的读取结束时回调用的block”,这个block拿到每一块读取好的Dispatch Data(这里指pipe data),然后进行合并处理。
参数:
channel 通道
offset 对于DISPATCH_IO_RANDOM 类型的通道,此参数指定要读取的信道的偏移量。
对于DISPATCH_IO_STREAM 类型的通道,此参数将被忽略,数据从当前位置读取。

length 从通道读取的字节数。指定size_max继续读取数据直到达到一个EOF。

如果处理程序与所做的参数设置为是的,一个空的数据对象,和一个0的错误代码,它意味着该通道达到了文件的结尾。

2.为指定的信道调度一个异步写操作。

void dispatch_io_write( dispatch_io_t channel, off_t offset, dispatch_data_t data, dispatch_queue_t queue, dispatch_io_handler_t io_handler);

该函数将指定的数据并提交io_handler块队列在操作的进度报告。如果处理程序的所做的参数设置为“不”,则意味着只有部分数据被写入。如果所做的参数设置为是的,这意味着写操作完成,处理程序将不会再次提交。如果操作成功,则处理程序的错误参数设置为0。

设置一次读取的大小
1.设置一次读取的最大字节

 void dispatch_io_set_high_water( dispatch_io_t channel, size_t high_water);

2.设置一次读取的最小字节

 void dispatch_io_set_low_water( dispatch_io_t channel, size_t low_water);

10. Dispatch Source

Dispatch Source是BSD系统内核惯有功能kqueue的包装。
kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XNU内核中发生的各种事件的方法中最优秀的一种。
Dispatch Source可处理以下事件:
DISPATCH_SOURCE_TYPE_DATA_ADD:变量增加,
DISPATCH_SOURCE_TYPE_DATA_OR:变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND:MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV:MACH端口接收
DISPATCH_SOURCE_TYPE_PROC:检测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ:可读取文件镜像
DISPATCH_SOURCE_TYPE_SIGNAL:接收信号
DISPATCH_SOURCE_TYPE_TIMER:定时器
DISPATCH_SOURCE_TYPE_VNODE:文件系统有变更
DISPATCH_SOURCE_TYPE_WRITE:可写入文件镜像

事件发生时,在指定的Dispatch Queue中可执行事件的处理。
Dispatch Source不同于Dispatch Queue,它是可取消的,而且取消时必须执行的处理可指定为会调用的Block形式。因此使用Dispatch Source实现XNU内核中发生的事件处理要比直接使用kqueue实现更为简单。在必须使用kqueue的情况下使用Dispatch Source比较简单。

Dispatch Source还需要新的篇幅来说明,下面两个例子参考《Dispatch Source备览》

10.1 读取文件的例子

10.2 定时器的例子

参考

iOS基础深入补完计划--GCD https://www.jianshu.com/p/96b93aa05bcdhttps://blog.csdn.net/growinggiant/article/details/41077221https://www.jianshu.com/p/d840fb75e668
GCD(Dispatch I/O) https://www.jianshu.com/p/da4710d76d33
GCD高级用法-Dispatch I/O https://blog.csdn.net/u014205965/article/details/45922471?utm_source=blogxgwz1

iOS开发多线程篇—GCD介绍https://www.cnblogs.com/wendingding/p/3806821.html

iOS开发 多线程的高级应用(一)https://www.jianshu.com/p/26a82224a3ff

GCD信号量-dispatch_semaphore_thttps://www.jianshu.com/p/24ffa819379c

理解GCD死锁https://www.jianshu.com/p/bbabef8aa1fe

iOS基础深入补完计划--NSOperationhttps://www.jianshu.com/p/8f42431fa466

iOS基础深入补完计划--多线程(面试题)汇总https://www.jianshu.com/p/3d430c25dd65

iOS 并行队列、串行队列和线程https://blog.csdn.net/lv_ruanruan/article/details/80266666
一个线程中相同串行队列里的任务会死锁。例如主队列上执行同步任务会因相互等待而死锁。但是在主线程上执行非主队列任务不会死锁,主线程会先去执行其他队列任务后再执行主队列的任务。
串行队列的同步任务有个类似barrier的行为,并且在当前线程执行,异步则会在非当前线程的另一个线程中执行。
并发队列的同步任务在当前线程依次执行,异步任务则会在多个线程中进行。

Categories:  ios 

« Dispatch Sources备览 一句话设计模式 »

Categories

Recent Posts

Copyright © 2014 - - Powered by MWeb    Theme by Octopress

一键复制

编辑

Web IDE

原始数据

按行查看

历史

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值