dispatch_block_t其实是一个自定义block类型node
typedef void (^dispatch_block_t)(void);
苹果对于它的注释至关有意思:
编程
若是不使用ARC,分配在堆上或者复制到堆上的block对象必定要经过发送release消息或者调用Block_release函数来释放。
block字面量的声明是在栈空间上,必定要杜绝这样的建立:
dispatch_block_t block; if (x) { block = ^{ printf("true\n"); }; } else { block = ^{ printf("false\n"); }; } block(); // unsafe!!!
这样其实至关于swift
if (x) { struct Block __tmp_1 = ...; // setup details block = &__tmp_1; } else { struct Block __tmp_2 = ...; // setup details block = &__tmp_2; }
这个示范中,栈变量的地址脱离了它声明时的做用域。这是一个经典的C语言bug。 取而代之的应该是:block字面量必须经过Block_copy函数或者发送copy消息复制到堆上。
dispatch_queue_t是任务执行的队列类型,它经过dispatch_queue_create建立,有两种类型
DISPATCH_QUEUE_SERIAL(NULL): 串行队列
DISPATCH_QUEUE_CONCURRENT: 并行队列数组
串行队列中的任务是有序执行的,并行队列中的任务是无序执行的,具体还要看以同步方式执行仍是以异步方式执行。缓存
两个特殊的队列:
main quue: dispatch_get_main_queue()获取。
global queue: dispatch_get_global_queue(0, 0)获取,这个函数的第一个参数是服务质量;第二个参数为保留值,始终传0.这个队列又叫作全局并行队列。
这两个队列的特色是:
放在主队列中的任务必定在主线程中执行。
放在全局队列中的任务必定是在子线程中执行。(大部分状况是对的,可是dispatch_sync方法优化为:使用当前线程)安全
这部分经过官方文档和一些任务执行顺序分析来理解这两个方法的区别,顺序分析时要注意如下几个因素:网络
1.环境:当前所处的线程
2.队列:将任务添加到了哪一种类型的队列
3.执行方式:同步仍是异步
多线程
这个方法阻塞,也就是说任务必定是添加到队列中以后而且执行完成以后,程序才会继续运行。
苹果的文档说的很清楚:Submits a block object for execution on a dispatch queue and waits until that block completes.
Submits a block to a dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.。所以下面的程序不管换成什么队列执行结果相同:并发
如下环境都是在主线程中app
dispatch_queue_t serialQueue = dispatch_queue_create("com.mikezh.serial.test", DISPATCH_QUEUE_SERIAL); dispatch_queue_t concurrentQueue = dispatch_queue_create("com.mikezh.concurrent.test", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); NSLog(@"begin"); for (NSUInteger i = 0; i < 10; i++) { dispatch_sync(concurrentQueue/*serialQueue globalQueue 都是同样的*/, ^{ if (i == 2) { sleep(2); } if (i == 5) { sleep(5); } NSLog(@"任务%zd, %@", i, [NSThread currentThread]); }); } NSLog(@"end");
begin 任务0, <NSThread: 0x600000077f80>{number = 1, name = main} 任务1, <NSThread: 0x600000077f80>{number = 1, name = main} 任务2, <NSThread: 0x600000077f80>{number = 1, name = main} 任务3, <NSThread: 0x600000077f80>{number = 1, name = main} 任务4, <NSThread: 0x600000077f80>{number = 1, name = main} 任务5, <NSThread: 0x600000077f80>{number = 1, name = main} 任务6, <NSThread: 0x600000077f80>{number = 1, name = main} 任务7, <NSThread: 0x600000077f80>{number = 1, name = main} 任务8, <NSThread: 0x600000077f80>{number = 1, name = main} 任务9, <NSThread: 0x600000077f80>{number = 1, name = main} end
这里global queue执行代码调度的线程取决于环境(可见global queue中任务必定是在子线程执行这个说法是错误的,这个在于dispatch_sync方法的优化:As an optimization, this function invokes the block on the current thread when possible.),例如:
dispatch_async(globalQueue, ^{ NSLog(@"begin"); for (NSUInteger i = 0; i < 10; i++) { dispatch_sync(globalQueue, ^{ if (i == 2) { sleep(2); } if (i == 5) { sleep(5); } NSLog(@"任务%zd, %@", i, [NSThread currentThread]); }); } NSLog(@"end"); });
begin 任务0, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务1, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务2, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务3, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务4, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务5, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务6, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务7, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务8, <NSThread: 0x61800006f880>{number = 3, name = (null)} 任务9, <NSThread: 0x61800006f880>{number = 3, name = (null)} end
另外,苹果也说明了什么状况下会形成死锁:在currentqueue中调用dispatch_sync方法,而且将任务添加到currentqueue中,也就是说下面的代码会形成死锁:
// 主线程、主队列、同步执行=====>死锁 dispatch_sync(mainQueue, ^{ });
dispatch_async(serialQueue, ^{ dispatch_sync(serialQueue, ^{ }); });
向queue提交异步执行的block并当即返回。
这个函数是向队列提交block的基本机制。
调用这个函数老是在提交block以后当即返回而且从不等待block的调用。
目标queue会参照其余的block来决定block是串行仍是并行执行。相互独立的串行队列参照别的串行队列来并行处理。
参数:queue
block提交到的queue.这个queue会被系统持有直到block运行完毕。
block
提交到目标调度queue中的block。这个函数会帮你执行Block_copy和Block_release。
dispatch_async能够用来分析任务执行时要考虑的就是:block提交的顺序,block开始执行的顺序。
1.环境中后面的代码不会等待block的执行
2.对于串行队列而言,block执行的顺序只能和执行的顺序相同,
对于并行队列而言,由于任务的执行是并行的,因此产生提交的block顺序和执行的顺序不一致的状况。
对于如下程序:
NSLog(@"begin"); for (NSUInteger i = 0; i < 10; i++) { dispatch_async(queue, ^{ if (i == 2) { sleep(2); } if (i == 5) { sleep(5); } NSLog(@"任务%zd, %@", i, [NSThread currentThread]); }); } NSLog(@"end");
若是queue是mainQueue
begin end 任务0, <NSThread: 0x60000006de80>{number = 1, name = main} 任务1, <NSThread: 0x60000006de80>{number = 1, name = main} 任务2, <NSThread: 0x60000006de80>{number = 1, name = main} 任务3, <NSThread: 0x60000006de80>{number = 1, name = main} 任务4, <NSThread: 0x60000006de80>{number = 1, name = main} 任务5, <NSThread: 0x60000006de80>{number = 1, name = main} 任务6, <NSThread: 0x60000006de80>{number = 1, name = main} 任务7, <NSThread: 0x60000006de80>{number = 1, name = main} 任务8, <NSThread: 0x60000006de80>{number = 1, name = main} 任务9, <NSThread: 0x60000006de80>{number = 1, name = main}
若是是serialQueue
begin end 任务0, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务1, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务2, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务3, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务4, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务5, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务6, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务7, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务8, <NSThread: 0x61000026a000>{number = 3, name = (null)} 任务9, <NSThread: 0x61000026a000>{number = 3, name = (null)}
若是是globalQueue
begin end 任务0, <NSThread: 0x60800007d6c0>{number = 3, name = (null)} 任务1, <NSThread: 0x61000007ca80>{number = 4, name = (null)} 任务4, <NSThread: 0x618000261040>{number = 6, name = (null)} 任务3, <NSThread: 0x60000007f780>{number = 5, name = (null)} 任务6, <NSThread: 0x60000007e0c0>{number = 7, name = (null)} 任务7, <NSThread: 0x61000007bfc0>{number = 8, name = (null)} 任务8, <NSThread: 0x60800007d6c0>{number = 3, name = (null)} 任务9, <NSThread: 0x60800007d540>{number = 9, name = (null)} 任务2, <NSThread: 0x60800007d8c0>{number = 10, name = (null)} 任务5, <NSThread: 0x600000262b00>{number = 11, name = (null)}
若是是concurrentQueue
begin end 任务1, <NSThread: 0x610000073a40>{number = 4, name = (null)} 任务0, <NSThread: 0x618000065040>{number = 3, name = (null)} 任务3, <NSThread: 0x600000068f80>{number = 5, name = (null)} 任务4, <NSThread: 0x618000067bc0>{number = 6, name = (null)} 任务6, <NSThread: 0x60800006ec80>{number = 7, name = (null)} 任务7, <NSThread: 0x6180000679c0>{number = 8, name = (null)} 任务8, <NSThread: 0x61000006cdc0>{number = 9, name = (null)} 任务9, <NSThread: 0x610000073a40>{number = 4, name = (null)} 任务2, <NSThread: 0x610000073c40>{number = 10, name = (null)} 任务5, <NSThread: 0x618000064d40>{number = 11, name = (null)}
dispatch_set_target_queue的做用是:为指定的object设置目标队列。
目标队列负责处理这个object。object最后执行所在的队列取决于目标队列。另外,修改目标队列会影响一些object的行为:
object为Dispatch queues:
这个object,也就是这个queue会继承目标队列的优先级。可使用dispatch_get_global_queue函数获取一个有期待的优先级的合适的目标队列。
若是你向串行队列提交block,同时这个串行队列的目标队列是一个不一样的串行队列,这个block相对于已经提交到目标队列中的其余block不会异步执行,对于设置一样目标队列的其余队列中的block也不会异步执行。
Important
若是你为一个队列修改了目标队列,你必须当心以免建立队列层级的循环(目标环)。
object为Dispatch sources:
目标队列为source指定了它的事件处理和取消处理的block将会提交到哪里。
object为Dispatch I/O channels:
目标队列为I/O channel指定了I/O操做将在哪里执行。这会影响到I/O操做的优先级。好比,若是channel的目标队列优先级设置为DISPATCH_QUEUE_PRIORITY_BACKGROUND,dispatch_io_read和dispatch_io_write函数进行的任何I/O操做都会在发生资源竞争时中止。
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue); dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(self.timer, ^{ NSLog(@"hahah--%@", [NSThread currentThread]); }); dispatch_set_target_queue(self.timer, mainQueue); // timer在主线程上执行 dispatch_resume(self.timer);
dispatch_set_target_queue方法的第一个参数object是dispatch_object类型却能够传递多种类型,这是为何呢?
// source.h DISPATCH_SOURCE_DECL(dispatch_source); // object.h // 非swift环境下 DISPATCH_DECL(name); // object.h 非swift环境下 #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object) // object.h 非swift环境下 #define OS_OBJECT_DECL_SUBCLASS(name, super) \ OS_OBJECT_DECL_IMPL(name, <OS_OBJECT_CLASS(super)>) // 下面的①②③是对这个宏的展开 #define OS_OBJECT_DECL_IMPL(name, ...) \ OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \ typedef NSObject<OS_OBJECT_CLASS(name)> \ * OS_OBJC_INDEPENDENT_CLASS name##_t // ① #define OS_OBJECT_DECL_PROTOCOL(name, ...) \ @protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \ @end // ② #define OS_OBJECT_CLASS(name) OS_##name // ③ #if __has_attribute(objc_independent_class) #define OS_OBJC_INDEPENDENT_CLASS __attribute__((objc_independent_class)) #endif // __has_attribute(objc_independent_class) #ifndef OS_OBJC_INDEPENDENT_CLASS #define OS_OBJC_INDEPENDENT_CLASS #endif
所以dispatch_source_t彻底展开就是:
@protocol OS_dispatch_source <OS_dispatch_object> @end typedef NSObject<OS_dispatch_source>* dispatch_source_t
DISPATCH_DECL(dispatch_io); OS_OBJECT_DECL_SUBCLASS(name, dispatch_object) // ...
所以dispatch_source_t彻底展开就是:
@protocol OS_dispatch_io <OS_dispatch_object> @end typedef NSObject<OS_dispatch_io>* dispatch_io_t
dispatch_queue_t就是
@protocol OS_dispatch_queue <OS_dispatch_object> @end typedef NSObject<OS_dispatch_queue>* dispatch_queue_t
相似的还有dispatch_semaphore、dispatch_data_t、dispatch_group_t这几个类型。
他们都是一个遵照相应协议的NSObject对象类型,这些协议的基协议OS_dispatch_object就是由dispatch_object_t声明的:
OS_OBJECT_DECL_CLASS(dispatch_object); #define OS_OBJECT_DECL_CLASS(name) \ OS_OBJECT_DECL(name) #define OS_OBJECT_DECL(name, ...) \ OS_OBJECT_DECL_IMPL(name, <NSObject>) #define OS_OBJECT_DECL_IMPL(name, ...) \ OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \ typedef NSObject<OS_OBJECT_CLASS(name)> \ * OS_OBJC_INDEPENDENT_CLASS name##_t
彻底展开就是:
@protocol OS_dispatch_object <NSObject> @end typedef NSObject<OS_dispatch_object> * dispatch_object_t
dispatch_after函数会在指定的时刻将block异步地添加到指定的队列。
支持传递DISPATCH_TIME_NOW做为when参数,可是不如调用dispatch_async更优。不能够传递DISPATCH_TIME_FOREVER。
要注意的是:
并非在指定的时间后执行处理,而是在指定时间追加block到队列中,由于mainQueue在主线程的runloop中执行,因此在好比每隔1/60秒执行的RunLoop中。block最快在指定时刻执行,最慢在指定时刻+1/60秒执行,而且在main queue中有大量处理追加或主线程的处理自己有延迟时,这个时间会更长。
这个方法的第一个参数是dispatch_time_t类型,它其实是:
typedef uint64_t dispatch_time_t;
它是对时间的一个抽象表示,0表明如今, DISPATCH_TIME_FOREVER表明着无限大,能够经过两个函数建立
/// 根据默认时钟建立一个时间,或者修改一个已存在的时间 /// OS X 默认时钟基于mach_absolute_time()函数 /// 参数when:须要修改的时间,若是传递0,这个函数会使用mach_absolute_time()返回值。 /// 参数delta:要添加的纳秒数 dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
/// 使用系统时间建立一个时间类型值 /// OS X 系统时钟基于gettimeofday(3)函数 /// 参数when:须要修改的时间或依据时间,是一个struct timespec类型指针,若是传递NULL,这个函数会使用gettimeofday(3)返回值。 /// 参数delta:要添加的纳秒数 dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
group对于多个任务结束后执行一些操做很是有用。Apple这样介绍:
一组block对象提交到一个队列中来异步调用。group是一个管理一系列block的机制。你的程序能够根据须要同步或者异步监控这些block。另外,group对于一些依赖其余操做完成的同步代码很是有用。
要注意的是:group中的block能够在不一样的queue中执行,每个block中能够添加更多block到group中
group会追踪有多少个未完成的block,GCD会持有group,直到全部相关的block所有执行完成。
举个例子:下载A、B、C三个文件,所有下载完成以后提示
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, globalQueue, ^{ NSLog(@"downloading A ..."); }); dispatch_group_async(group, globalQueue, ^{ NSLog(@"downloading B ..."); }); dispatch_group_async(group, globalQueue, ^{ NSLog(@"downloading C ..."); }); dispatch_group_notify(group, mainQueue, ^{ NSLog(@"下载完成"); });
若是是想等待所有执行完成以后再进行其余的代码可使用
dispatch_group_wait(group, time)
注意这个方法是阻塞方法,它有个特色:会一直等待直到到达等待的时间 或 任务执行完成才返回。
对于它的返回值,若是返回值为0,表明group中的任务已经所有完成,非0则没有完成。
但实际开发中有这样的场景,好比进行一组网络请求任务,每个任务都是异步任务,而请求完成并将数据解析完毕咱们才认为是任务的完成,而这些过程又有多是跨线程的。这时候就要使用dispatch_group_enter和dispatch_group_leave组合,经过它们能够对group进行更细粒度的控制。这两个函数都是线程安全的,对应着添加和移除任务,所以使用时必需要成对出现。
dispatch_group_enter(group); dispatch_group_async(group, globalQueue, ^{ dispatch_async(globalQueue, ^{ NSLog(@"downloading A ..."); dispatch_group_leave(group); }); }); dispatch_group_async(group, globalQueue, ^{ dispatch_group_enter(group); dispatch_async(globalQueue, ^{ NSLog(@"downloading B ..."); dispatch_group_leave(group); }); }); dispatch_group_enter(group); dispatch_group_async(group, globalQueue, ^{ dispatch_async(globalQueue, ^{ NSLog(@"downloading C ..."); dispatch_group_leave(group); }); }); dispatch_group_notify(group, mainQueue, ^{ NSLog(@"下载完成"); });
咱们知道在对于并行队列,使用async方法执行任务,任务被添加到队列中是有序的,可是执行无序。但有这么一个场景:对于数据读操做并发执行没有问题,可对于写操做来讲却要控制写过程当中再也不进行读操做,以免数据竞争问题。相似的场景不少,大致归结为在许多并发任务中,有1个任务在执行的时候必须保证其余的任务等待其执行完毕.诸如此类的问题可使用dispatch_barrier_asyn函数解决。
提交一个异步执行的barrier block并当即返回。
调用这个函数老是在block被提交以后当即返回,而从不等待block的执行。当barrier block到达自定义并发队列的队头时,它不会被当即执行。它会等待直到当前正在执行的lock执行完毕,到这时,barrier block才会执行。
任何在barrier block后套面提交的block也不会执行,直到barrier block执行完毕。
你指定的队列应当是一个使用dispatch_queue_create函数本身建立的并行队列。若是传给这个函数一个串行队列或
global并行队列,这个函数会像dispatch_async函数同样。
下图能够很好的说明这个函数的做用
dispatch_async(concurrentQueue, ^{ NSLog(@"1"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"2"); }); dispatch_barrier_async(concurrentQueue, ^{ sleep(5); NSLog(@"barrier"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"3"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"4"); });
能够测试打印结果:
2 1 barrier 3 4 // 一、2无序必定在以前, 三、4无序必定在以后
提交一个barrier block并等待这个block执行完毕。
提交一个barrier block到队列用来同步执行。不一样于dispatch_barrier_async,这个函数直到barrier block执行完毕才返回。目标队列是当前队列会发生死锁。当barrier block到达自定义并发队列的队头时,它不会被当即执行。它会等待直到当前正在执行的lock执行完毕,到这时,barrier block才会执行。任何在barrier block后套面提交的block也不会执行,直到barrier block执行完毕。
你指定的队列应当是一个使用dispatch_queue_create函数本身建立的并行队列。若是传给这个函数一个串行队列或
global并行队列,这个函数会像dispatch_sync函数同样。
不一样于dispatch_barrier_async,系统不会持有目标队列。由于调用这个函数是同步的,它借用了调用着的引用。并且也不会对block进行Block_copy操做。
做为优化,这个函数尽量在当前线程调用barrier block
能够看到:最后面的优化说明和dispatch_sync方法彻底一致。
这个函数和dispatch_barrier_async的区别就在于可否阻塞当前的线程。
测试:
dispatch_async(concurrentQueue, ^{ NSLog(@"1"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"2"); }); // 阻塞 dispatch_barrier_sync(concurrentQueue, ^{ sleep(5); // 若是当前环境为主线程,则界面frozen NSLog(@"barrier sync执行"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"3"); }); dispatch_async(concurrentQueue, ^{ NSLog(@"4"); });
为执行多个操做向队列提交一个block,而且在返回以前等待全部的操做完成。若是目标队列是dispatch_get_global_queue返回的并行队列,block会被并行执行,所以它必须是可重入安全的。配合并行队列使用这个方法对于一个循环的高效并发来讲很是有用。
NSLog(@"begin"); dispatch_apply(5, globalQueue, ^(size_t index) { NSLog(@"%zd", index); }); NSLog(@"end");
结果为:
begin 0 1 2 4 3 end
能够利用这个方法高效地处理数组中的数据,不过要注意:虽然会等待全部的任务执行完成才返回,但每一个任务的执行是异步无序的。
dispatch_apply(array.count, globalQueue, ^(size_t index) { id element = array[index]; // handler });
暂停在dispatch object上的block的执行。
经过暂停一个dispatch object,你的程序能够暂时阻止与这个object有关的任何block的执行。这个暂停发生在调用方法时全部正在执行的block完成以后。调用这个函数会递增object的暂停数,而调用dispatch_resume会少这个计数,因此你必须用一个相匹配的dispatch_resume调用来平衡每一次的dispatch_suspend调用。
一旦object被恢复,任何提交到队列中的block或者经过dispatch source观察的事件就会执行。
dispatch_suspend(queue) dispatch_suspend(timer)
继续执行dispatch object上的block。
调用这个方法会递减暂停的队列数或暂停的事件源对象。当计数大于0时,对象会保持暂停。当暂停数重置为0,任何提交到队列中的block或者经过dispatch source观察的事件会被执行。
有一个例外:每次调用dispatch_resume必须是来平衡调用dispatch_suspend的。新的经过dispatch_source_create函数返回的事件源对象有一个值为1暂停计数,所以必须在事件执行以前resume。这个方法使你的程序在事件分发以前完整地配置事件源对象。对于其余状况,都不容许比调用dispatch_suspend的次数多,那会致使负的暂停计数。
dispatch_resume(queue) dispatch_resume(timer)
semaphore即信号量。 在多道程序环境下,操做系统如何实现进程之间的同步和互斥显得极为重要。荷兰学者Dijkstra给出了一种解决并发进程间互斥与同步关系的通用方法,即信号量机制。
信号量是一个具备非负初值的整型变量,而且有一个队列与它关联。信号量除初始化外,仅能经过P、V 两个操做来访问,这两个操做都由原语组成,即在执行过程当中不可被中断,也就是说,当一个进程在修改某个信号量时, 没有其余进程可同时对该信号量进行修改。P操做信号量减1,若是信号量≥0,表示能够继续执行;若是<0就要阻塞等待,直到信号量>=0。V操做信号量加1。
信号量能够模拟现实中相似于通行证的概念,即信号量>=0能够通行,而信号量<0时则须要等待增长才能够以通行。所以信号量机制编程必涉及三个函数,建立信号量、增长信号量、减小信号量。
dispatch_semaphore_t dispatch_semaphore_create(long value)
建立一个信号量,参数为初始值。
传入0适用于两个线程须要解决对于一个资源的竞争。
传入一个大于0的值适用于管理一个有限的资源池,这个池子的大小与传入的值相等。
当你的程序再也不须要信号量时,应该调用dispatch_release来释放它对信号量对象的引用并最终释放内存。(ARC会帮助处理,所以不要手动调用dispatch_release()函数)
long dispatch_semaphopre_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待 (减小)一个信号量.
递减信号量计数。若是递减以后的结果值小于0,这个方法会在返回以前一直等待一个signal信号。
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
signal或者说递增一个信号量。
递增信号量计数。若是以前的值小于0,这个函数会异步地唤起一个正在dispatch_semaphore_wait函数中等待的线程。
模拟一次资源竞争的问题,在多个线程中操做同一个数据是很是常见的资源竞争的状况,这样很是容易引发数据不一致,有时候应用会异常结束。咱们使用dispatch_barrier_async能够解决这个问题,可是它是对整块block任务的隔离,而并无细微到对要操做的数据这个粒度的限制。例如使用可变数组模拟多线程写数据的状况(只是模拟写数据过程,不考虑顺序),
NSUInteger count = 10; NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; i++) { dispatch_async(globalQueue, ^{ [mutableArray addObject:[NSNumber numberWithInteger:i]]; }); }
在多线程中更新NSMutableArray, 这段代码异常率是极高的。可使用信号量机制进行保证线程安全性,任何一个正在写的操做必需要完成以后,才能进行下一个写的操做:
NSUInteger count = 10; self.mutableArray = [NSMutableArray arrayWithCapacity:count]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); for (NSUInteger i = 0; i < count; i++) { dispatch_async(globalQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [self.mutableArray addObject:[NSNumber numberWithInt:i]]; // 执行到这里说明没有阻塞,即信号量依然>=0 dispatch_semaphore_signal(semaphore); }); }
void dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block);
在程序的生命周期只执行block一次
这个函数对程序中的全局数据(单例)的初始化很是有用。老是在使用或测试任何经过block初始化的变量以前使用这个函数。
若是在多个线程中同时调用,这个函数会同步地等待知道block执行完成。
这个predicate参数必须指向一个保存在全局区或静态区的变量。使用自动存储或动态存储变量(包括OC实例变量)的predicate结果是未知的。
static Singleton *instance; + (instancetype)shareInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!instance) { instance = [[[self class] alloc] init]; } }); return instance; }
读取较大文件时,若是将文件分红合适的大小并使用Global Queue并行读取的话应该会比通常的读取速度快很多,现今的输入输出硬件已经能够作到一次使用多个线程更快地并列读取了,能实现这一功能的就是dispatch_io和dispatch_data.
使用dispatch_io读写文件能够将1个文件固定大小分为快分别在多个线程read/write.
dispatch_async (queue, ^{/*读取 0 ~ 8191 字节*/}); dispatch_async (queue, ^{/*读取 8192 ~ 16383 字节*/}); dispatch_async (queue, ^{/*读取 16384 ~ 24575 字节*/}); dispatch_async (queue, ^{/*读取 24576 ~ 36767 字节*/});
这里有一个能够分块读取,而后拼装为NSData的方法:
void read_file(int fd, void(^completion)(NSData *data)) { NSMutableData *data = [NSMutableData data]; dispatch_queue_t pipe_q; dispatch_io_t pipe_channel; pipe_q = dispatch_queue_create("PipeQ", NULL); // pipe_q = dispatch_get_main_queue(); pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){ close(fd); }); 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){ NSLog(@"%@", [NSThread currentThread]); if (err == 0) { size_t len = dispatch_data_get_size(pipedata); if (len > 0) { const char *bytes = NULL; (void)dispatch_data_create_map(pipedata, (const void **)&bytes, &len); [data appendBytes:bytes length:len]; } } if (done && completion) { completion(data.copy); } }); }
使用时,传入文件描述便可:
int fd = open("/Users/Mike/Desktop/a.txt", O_RDWR); read_file(fd, ^(NSData *data) { NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); });
GCD中除了主要的Dispatch Queue外,还有不太引人注目的Dispatch Source。它是BSD系统内核惯有功能kqueue的包装。
kqueue是在XNU内核发生各类事件时,在应用程序编程方执行处理的技术。其CPU负荷很是小,尽可能不占用资源,kqueue能够说是应用程序处理XNU内核发生的各类事件的方法中最优秀的一种。
dispatch_source使用流程大体以下:
1.经过dispatch_source_create()
函数建立一个source
2.经过dispatch_source_set_event_handler()
函数为source指定处理的block。
经过dispatch_source_set_cancel_handler()
函数为source指定取消的回调,这个回调会经过dispatch_source_cancel()
函数的调用触发。
3.经过dispatch_resume()
函数启动source
建立一个新的source来管理底层的系统对象,同时自动提交handler block到GCD队列中来响应事件。
GCD source不是可重入的。任何在source暂停时或者在handler block正在执行时接收到的事件都会合并而后在source恢复或者事件handler block返回以后分发。GCD source建立时的状态是挂起的。在建立以后或者设置一些属性(好比handler或者context)以后,你的程序必须调用dispatch_resume来开始这个事件的分发。
若是你的app没有使用ARC,你应该在再也不使用source的时候调用dispatch_release来释放它
Important
事件source的建立时异步的,因此要搞清楚被监控的系统句柄产生的竞争条件。好比,若是一个source是为一个进程建立的,同时这个进程在source建立以前就存在了,那么任何设定的取消处理都不会被调用。
参数
type
source的类型。必须是下面列出的source类型常量之一
type | 内容 |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 变量增长 |
DISPATCH_SOURCE_TYPE_DATA_OR | 变量OR |
DISPATCH_SOURCE_TYPE_MACH_RECV | Mach端口发送 |
DISPATCH_SOURCE_TYPE_MACH_SEND | 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_SOURCE_TYPE_MEMORYPRESSURE | 内存压力 |
handle
要监控的底层系统句柄。这个参数的具体值依赖于type参数常量。
mask
指望的事件指定的flags的掩码。这个参数的具体值依赖于type参数常量。
queue
事件处理的block提交到的GCD队列。
Returns
建立成功返回新建立的source,不然返回NULL。
为指定的source设置事件处理block。
事件处理(若是设置了)会提交到source的目标队列来响应事件的发生。
为指定的source设置事件取消block。
取消block(若是设置了)会提交到source的目标队列来响应dispatch_source_cancel的调用,这个响应发生在系统释放了全部对source的底层句柄,同时,source事件处理的block已经返回了
Important
为了能安全地关闭文件描述或者销毁Mach port, 取消的处理对文件描述或者port来讲是必须的。在取消处理执行以前关闭描述和port会产生竞争条件。当source事件处理仍在运行时,若是有一个新的描述被分配了与最近关闭的描述一样的值,事件处理可能会使用错误的描述读写数据。
异步地取消source,阻止对事件处理block的再次调用。
取消操做阻止任何对事件处理block的再次调用,可是不会打断正在执行的事件处理。一旦事件处理block执行完毕,这个可选的取消处理会提交到目标队列。(事件处理至少执行一次??)
取消操做会在时间处理执行完成时提交到目标队列,意味着关闭source的句柄(文件描述或者mach port)是安全的。
这个可选的取消处理只会在系统释放了全部的对底层系统对象(文件描述或mach prots)的引用以后才提交到目标队列。所以,取消处理是一个关闭和释放这些系统对象很是方便的地方。要注意的是,若是文件描述或mach port最近被被source对象追踪,在取消操做执行以前关闭或者释放它们是无效的.
为一个timer source设置开始时间,间隔,误差。
你的程序能够根据须要在一个timer source对象上屡次调用这个函数来重置时间间隔。
开始时间这个参数也决定了这个timer上将使用什么时钟。若是开始时间是DISPATCH_TIME_NOW或者是dispatch_time函数建立的,这个timer基于mach_absolute_time。不然,timer的开始时间是dispatch_walltime建立的,这个timer基于gettimeofday(3)。
误差参数是程序时间值的微量,它以毫秒为单位,为了提高系统性能和解决耗电量,它能够大到系统将timer延迟到与其余系统活动结合使用。例如,一个每5分钟执行一个周期性任务,而程序可能会使用一个最多30秒的leeway值。要注意的是:对全部的timer而言,即便leeway值指定为0,一些潜在问题也是正常的。
调用这个函数不会对已经取消的timer source有做用。
参数
start
timer开始的时间。查看 dispatch_time 和 dispatch_walltime 获取更多信息。
interval
以毫秒为单位的时间间隔
leeway
系统能够延迟这个timer的时间值,以毫秒为单位。
测试指定的source是否已经被取消。
你的程序会使用这个函数来测试GCD source对象是否已经被经过调用dispatch_source_cancel的方式取消。若是dispatch_source_cancel已经调用过了,这个函数会马上返回一个非0值,若是没有被取消则返回0.
返回数据。
要在事件处理的block中调用这个函数。在外面调用会发生意想不到的结果。
返回值(unsigned long )
根据source的type的不一样会有不一样的返回值,共有如下几种:
type | 返回值 |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 程序定义的数据 |
DISPATCH_SOURCE_TYPE_DATA_OR | 程序定义的数据 |
DISPATCH_SOURCE_TYPE_MACH_SEND | Dispatch Source Mach Send Event Flags |
DISPATCH_SOURCE_TYPE_MACH_RECV | 不适用 |
DISPATCH_SOURCE_TYPE_PROC | Dispatch Source Process Event Flags |
DISPATCH_SOURCE_TYPE_READ | 预估可读字节数 |
DISPATCH_SOURCE_TYPE_SIGNAL | 上次处理执行后的分发的signal数目 |
DISPATCH_SOURCE_TYPE_TIMER | 上次处理执行以后,timer启动后执行的次数 |
DISPATCH_SOURCE_TYPE_VNODE | Dispatch Source Vnode Event Flags |
Dispatch Source Memory Pressure Event Flags | 可用的预估缓存空间 |
返回source监控的事件的掩码。
这个掩码是一个事件source监控的相关事件的位掩码。任何在这个事件掩码里没有指定的事件都胡被忽略,同时不会为这些事件提交事件处理block。
更详细的信息 查看flag描述常量。
返回值
返回值根据source的type不一样,会是如下flag集合中的一种:
type | 返回值 |
---|---|
DISPATCH_SOURCE_TYPE_MACH_SEND | Dispatch Source Mach Send Event Flags |
DISPATCH_SOURCE_TYPE_PROC | Dispatch Source Process Event Flags |
DISPATCH_SOURCE_TYPE_VNODE | Dispatch Source Vnode Event Flags |
返回与指定source关联的底层系统句柄。
这个返回的句柄是一个对source监控的底层系统对象的引用。
返回值
这个返回值根据source的类型不一样而不一样,它回事如下句柄中的一种:
| DISPATCH_SOURCE_TYPE_MACH_SEND | mach port (mach_port_t) |
| DISPATCH_SOURCE_TYPE_MACH_RECV | mach port (mach_port_t) |
| DISPATCH_SOURCE_TYPE_PROC | process identifier (pid_t) |
| DISPATCH_SOURCE_TYPE_READ | file descriptor (int) |
| DISPATCH_SOURCE_TYPE_SIGNAL | signal number (int) |
| DISPATCH_SOURCE_TYPE_VNODE | file descriptor (int) |
| Dispatch Source Memory Pressure Event Flags | file descriptor (int) |
合并数据到GCD source,这个source的类型为DISPATCH_SOURCE_TYPE_DATA_ADD
或者DISPATCH_SOURCE_TYPE_DATA_OR
,而后将事件处理提交到目标队列。
你的程序使用这个函数来处理DISPATCH_SOURCE_TYPE_DATA_ADD
类型或DISPATCH_SOURCE_TYPE_DATA_OR
类型的事件
参数
value
使用逻辑或、逻辑与组合的source类型。传0没有任何做用,也不会提交处理block。
有两个串行队列
dispatch_queue_t queueA = dispatch_queue_create("com.mikezh.queueA", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queueB = dispatch_queue_create("com.mikezh.queueB", DISPATCH_QUEUE_SERIAL);
下面会发生死锁:
dispatch_sync(queueA, ^{ dispatch_sync(queueB, ^{ dispatch_block_t block = ^{ //do something }; if (dispatch_get_current_queue() == queueA) { block(); }else{ dispatch_sync(queueA, block); // 程序崩溃在这里 } }); });
为何会这里会发生死锁:
首先这里的队列嵌套关系以下所示:
咱们使用
if (dispatch_get_current_queue() == queueA)
进行判断的目的是为了防止在当前队列是queueA的状况下,向queueA中添加同步执行的block,由于这样会发生死锁。可是队列嵌套关系代表,当前所在的队列queueB被queueA嵌套,可是糟糕的是:dispatch_get_current_queue函数只能返回最内层的队列queueB,因此这个判断的结果不能让咱们完成起初的目的了。
这里已经充分说明了dispatch_get_current_queue的弊端:咱们想获知block执行的环境是否被某个队列嵌套来避免死锁,而它只是简单地只拿到最内层的队列的功能没法解决这个问题。
在说一些解决的方法以前,看一个上面一个功能的等价写法(若是不理解能够查看dispatch_set_target_queue部分):
dispatch_set_target_queue(queueB, queueA); dispatch_sync(queueB, ^{ dispatch_block_t block = ^{ //do something }; if (dispatch_get_current_queue() == queueA) { block(); }else{ dispatch_sync(queueA, block); // 程序崩溃在这里 } });
依然是死锁的。
下面咱们来介绍一种可是若是使用specific来判断当前的队列,就不会死锁:
dispatch_set_target_queue(queueB, queueA); static int specificKey; CFStringRef specificValue = CFSTR("at hierarchy under queueA"); dispatch_queue_set_specific(queueA, &specificKey, (void*)specificValue, (dispatch_function_t)CFRelease); dispatch_sync(queueB, ^{ dispatch_block_t block = ^{ //do something }; CFStringRef retrievedValue = dispatch_get_specific(&specificKey); if (retrievedValue == specificValue) { block(); // 程序走的这条分支,而不是下面的 } else { dispatch_sync(queueA, block); } });
咱们能够经过dispatch_get_specific函数准确得知在队列层级关系中是否存在specificKey指定的值,也就是说加入根据根据指定的键获取不到关联数据,那么系统会沿着层级体系向上查找,知道找到数据或到达根队列位置。这里要指出的是,层级里地位最高的那个队列老是全局并发队列。这个过程以下图所示:
因此上面的代码执行到dispatch_get_specific时会在queueA找到以前设定好的值,而后返回。这时队列的层级关系中得知存在queueA队列,直接调用block()
,而再也不走下面的分支dispatch_sync(queueA, block);
,从而有效避免了死锁。