dispatch_once
单次函数通常用来建立单例或者是执行只须要执行一次的程序。bash
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"==只会执行一次的代码==");
});
void dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
复制代码
dispatch_once
会保证block
中的程序只执行一次,而且即便在多线程的环境下,dispatch_once
也能够保证线程安全。多线程
dispatch_apply
dispatch_apply
函数会按照指定的次数将指任务添加到指定的队列中进行执行。不管是在串行队列,仍是并发队列中,dispatch_apply
都会等待所有任务执行完毕。闭包
若是是在串行队列中使用dispatch_apply
,会按顺序同步执行,就和普通的for
循环相似;若是是在异步队列中使用,下标可能不是按顺序来的。并发
void
dispatch_apply(size_t iterations,
dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
DISPATCH_NOESCAPE void (^block)(size_t));
复制代码
iterations
:执行迭代的次数queue
:执行迭代的队列,建议使用DISPATCH_APPLY_AUTO
,会自动调用合适的线程队列void (^block)(size_t))
:迭代的结果回调dispatch_after
延迟函数的做用是在指定的队列中,按照给定的时间执行一个操做。app
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
dispatch_block_t block);
复制代码
dispatch_time_t when
:指定执行任务的时间。
DISPATCH_TIME_NOW
,可是不推荐,由于该函数调用了dispatch_async
dispatch_time
或者dispatch_walltime
自定义时间:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC))
DISPATCH_TIME_FOREVER
dispatch_queue_t queue
:指定队列,执行任务的队列。dispatch_block_t block
:要执行的任务,不能传NULL
。dispatch_group
经过Dispatch Group
,咱们能够将多个任务放入一个组中,而且可让他们在同一队列或不一样队列上异步执行,执行完成以后,再执行其余的依赖于这些任务的操做。异步
相关API
:async
dispatch_group_t dispatch_group_create(void);
复制代码
void dispatch_group_enter(dispatch_group_t group);
复制代码
void dispatch_group_leave(dispatch_group_t group);
复制代码
timeout
归零才会继续下一步long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
复制代码
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
复制代码
下面咱们经过一个例子来看一下dispatch_group
的使用:函数
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"==1==");
});
dispatch_async(queue, ^{
NSLog(@"==2==");
});
dispatch_async(queue, ^{
NSLog(@"==3==");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"===4=");
});
复制代码
运行程序,控制台输出:post
能够看出这并非咱们想要的结果。对程序进行修改,继续运行:
一样使用dispatch_group_wait
也会获得相应的结果:
可是dispatch_group_wait
会阻塞以后的操做,好比咱们在组通知以后还执行了NSLog(@"==5==")
,组任务并无阻塞到它的执行,而dispatch_group_wait
就会阻塞。
注意,dispatch_group_enter
和dispatch_group_leave
必须成对出现,不然会形成死锁。
dispatch_barrier
栅栏函数分为dispatch_barrier_async
和dispatch_barrier_sync
函数,这两个函数既有共同点,又有不一样点:
dispatch_barrier_sync
将本身的任务插入到队列的时候,须要等待本身的任务结束以后才会继续插入被写在它后面的任务,而后执行它们dispatch_barrier_async
将本身的任务插入到队列以后,不会等待本身的任务结束,它会继续把后面的任务插入到队列,而后等待本身的任务结束后才执行后面任务。下面咱们配合一个例子说明一下:
- (void)barrierAsync {
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"--1--");
});
dispatch_async(queue, ^{
NSLog(@"--2--");
});
dispatch_barrier_async(queue, ^{
NSLog(@"--barrier_async--%@--",[NSThread currentThread]);
sleep(2);
});
NSLog(@"=======barrierAsync=======");
dispatch_async(queue, ^{
NSLog(@"--3--");
});
dispatch_async(queue, ^{
NSLog(@"--4--");
});
dispatch_async(queue, ^{
NSLog(@"--5--");
});
}
复制代码
运行程序:
将dispatch_barrier_async
函数改成dispatch_barrier_sync
,而后运行程序:
经过打印结果能够看出栅栏函数不论是同步异步,都会对当前队列中的任务起到隔离做用,就是会让栅栏以前的多线程操做先执行,让栅栏以后的多线程操做后执行。不一样的是dispatch_barrier_async
函数以后的多线程操做都是并发执行,而dispatch_barrier_sync
以后的操做都是同步执行,因此咱们打印的barrierAsync
的执行顺序和barrierSync
不一样。
简而言之,dispatch_barrier_sync
和dispatch_barrier_async
都会隔离队列中栅栏先后的任务,不一样的是会不会阻塞当前队列。因此栅栏函数和其拦截的任务必须是同一队列的,否则没有阻塞效果。因此在AFN
中使用栅栏函数没有效果,AFN
本身维护了一个串行队里,除非使用这个队列才会起做用。
注意,当咱们在主线程中调用任务,并且将同步栅栏函数也添加到主队列中,会发生死锁现象。使用栅栏函数要使用自定义队列,防止阻塞、死锁。
dispatch_semaphore_t
一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问以前,加上信号量的处理,则可告知系统按照咱们指定的信号量数量来执行多个线程。
相关API
:
NULL
,该参数控制当前能开启的线程数量。dispatch_semaphore_t dispatch_semaphore_create(long value)
复制代码
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
复制代码
dispatch_semaphore_t dsema
: 信号量。若是传入的dsema
大于0,就继续向下执行,并将信号量减1;若是dsema
等于0,阻塞当前线程等待资源被dispatch_semaphore_signal
释放。若是等到了信号量,继续向下执行并将信号量减1,若是一直没有等到信号量,就等到timeout
再继续执行。
dispatch_time_t timeout
: 超时,阻塞线程的时长。通常传DISPATCH_TIME_FOREVER
或者DISPATCH_TIME_NOW
,也能够自定义。dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 1*100*100*100);
若是成功则返回0,超时会返回其余值
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
复制代码
减小和增长信号量一般成对使用,使用的顺序是先减小信号量(wait)
而后再增长信号量(signal)
。
下面咱们结合一个例子,说明一下信号量的使用:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
复制代码
运行程序,控制台输出:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
复制代码
将建立的信号量改成3,或者大于3:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
复制代码
同理,咱们还能够将例子中的并发任务改成同步任务。能够得出以下结论:
再来看一个例子:
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
NSLog(@"==a==%d==", a);
复制代码
因为异步线程的问题,咱们打印a
的值,多是大于等于5,此时依靠信号量就能够控制让循环外输出a=5
。以下:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
NSLog(@"==a==%d==", a);
复制代码
关于信号量的时候,咱们须要注意的是防止线程被阻塞,当执行dispatch_semaphore_wait
方法的时候必定要保证传入的信号量大于0。
dispatch_source
当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,而后能够作其余的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。也就是用GCD
的函数指定一个但愿监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数做为回调函数,而后再指定一个该回调函数执行的队列便可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数做为一个任务放入指定的队列中执行。
相关的API
:
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
复制代码
void
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
复制代码
void
dispatch_source_merge_data(dispatch_source_t source, unsigned long value);
复制代码
unsigned long
dispatch_source_get_data(dispatch_source_t source);
复制代码
获取的数据类型和源事件的类型相关:
dispatch_source
,返回的是读到文件内容的字节数。dispatch_source
,返回的是文件是否可写的标识符,正数表示可写,负数表示不可写。dispatch_source
,返回的是监听到的有更改的文件属性,用常量表示,好比DISPATCH_VNODE_RENAME
等。dispatch_source
,返回监听到的进程状态,用常量表示,好比DISPATCH_PROC_EXIT
等。Mach
端口类型的dispatch_source
,返回Mach
端口的状态,用常量表示,好比DISPATCH_MACH_SEND_DEAD
等。dispatch_source
,返回使用dispatch_source_merge_data
函数设置的数据。void
dispatch_resume(dispatch_object_t object);
复制代码
void
dispatch_suspend(dispatch_object_t object);
复制代码
dispatch_source_type_t type
:设置dispatch_source
方法的类型uintptr_t handle
:取决于要监听的事件类型,好比若是是监听Mach
端口相关的事件,那么该参数就是mach_port_t
类型的Mach
端口号,若是是监听事件变量数据类型的事件那么该参数就不须要,设置为0就能够了。unsigned long mask
:取决于要监听的事件类型dispatch_queue_t _Nullable queue
:执行的队列,默认为全局队列dispatch_source_type_t
的取值以下:
DISPATCH_SOURCE_TYPE_DATA_ADD
:属于自定义事件,能够经过dispatch_source_get_data
函数获取事件变量数据,在咱们自定义的方法中能够调用dispatch_source_merge_data
函数向dispatch_source
设置数据。DISPATCH_SOURCE_TYPE_DATA_OR
:属于自定义事件,用法同DISPATCH_SOURCE_TYPE_DATA_ADD
。DISPATCH_SOURCE_TYPE_MACH_SEND
:Mach
端口发送事件。DISPATCH_SOURCE_TYPE_MACH_RECV
:Mach
端口接收事件。DISPATCH_SOURCE_TYPE_PROC
:与进程相关的事件。DISPATCH_SOURCE_TYPE_READ
:读文件事件。DISPATCH_SOURCE_TYPE_WRITE
:写文件事件。DISPATCH_SOURCE_TYPE_VNODE
:文件属性更改事件。DISPATCH_SOURCE_TYPE_SIGNAL
:接收信号事件。DISPATCH_SOURCE_TYPE_TIMER
:定时器事件。DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
:内存压力事件。下面咱们结合一个例子,具体的说明一下使用:
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
- (void)initSource {
self.queue = dispatch_queue_create("soureQueue", 0);
// 建立soure事件
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// 监听soure事件发生变化
dispatch_source_set_event_handler(self.source, ^{
// 获取source事件的值
NSUInteger value = dispatch_source_get_data(self.source);
self.totalComplete += value;
NSLog(@"进度:%.2f", self.totalComplete/100.0);
});
// 启动监听
dispatch_resume(self.source);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (NSUInteger index = 0; index < 100; index++) {
dispatch_async(self.queue, ^{
sleep(1);
// 设置source事件的数据
dispatch_source_merge_data(self.source, 1);
});
}
}
复制代码
运行程序:
dispatch_once
dispatch_after
是异步执行的dispatch_apply
dispatch_group
dispatch_group_enter
和dispatch_group_leave
必须成对出现,不然会形成死锁enter
后leave
dispatch_group_wait
会阻塞当前线程dispatch_barrier
dispatch_semaphore
dispatch_semaphore_wait
的参数为0的时候会堵塞线程dispatch_source
dispatch_source
的基本操做source
的类型参考资料:
官方文档
iOS 多线程:『GCD』详尽总结