dispatch_sync / dispatch_async 同步与异步
dispatch_sync 提交 block 以供同步执行。这个接口会等到 block 执行结束才返回,所以不需要复制 block。
可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性
sync 和 async 区别
同步函数不论是并发队列还是串行队列,都不会开线程。一个任务开始后等待任务完成后返回
异步函数并发队列能开启多个线程,串行队列开启一个线程。异步方法会立即返回,不等待任务完成。
dispatch_queue_create 队列的创建或获取
GCD的所有队列都是先进先出的,它的队列主要分三种,主队列 main_queue、全局并发队列 global_queue、自定义队列
串行队列:队列中的任务必须在前一个任务结束后才能执行,包括 DISPATCH_QUEUE_SERIAL 和 主队列
并发队列:队列中的任务必须在前一个任务开始后才能执行,包括 DISPATCH_QUEUE_CONCURRENT 和 全局并发队列
// dispatch_queue_create
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
// Serial dispatch queue,Executed on the main thread
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
// How to get a global dispatch queue of high priority
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// How to get a global dispatch queue of default priority
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// How to get a global dispatch queue of low priority
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// How to get a global dispatch queue of background priority
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
将上述的同步、异步、串行、并发四种方式组合成以下几种情况
同步串行:不开启新线程,串行执行队列中的任务
同步并发:不开启新线程,串行执行队列中的任务,并发失效
异步串行:开启新线程,串行执行队列中的任务
异步并发:开启新线程,并发执行任务
另外主队列中会有些特殊:
同步主队列:主线程调用会导致死锁;其他线程调用,不开启新线程,串行执行任务
异步主队列:不开启新线程,串行执行任务
例如,如果在调用该接口在当前 queue 上指派任务,就会导致 deadlock。以下是死锁的例子
- (void)deadLock1 {
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
// 主线程中同步一个主队列,sync 是后加入到队列的,它阻止了主线程,同时 sync 等待主线程执行完毕后才能执行 block,主线程等待 sync 执行后才能执行,造成死锁
NSLog(@"1");
});
}
}
- (void)deadLock2 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_sync(serialQueue,^{
dispatch_sync(serialQueue,^{
// 串行队列同步一个串行队列,死锁
NSLog(@"1");
});
});
}
- (void)deadLock3 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_async(serialQueue, ^{
// 串行队列里面同步一个串行队列就会死锁
dispatch_sync(serialQueue, ^{
NSLog(@"1");
});
});
}
全局并发队列可以设置优先级,对于 DISPATCH_QUEUE_PRIORITY_BACKGROUND 级别,文档解释说 Such a thread has the lowest priority and any disk I/O is throttled to minimize the impact on the system,也就是该级别最低,文件的读写等可以视情况使用该级别
更新一些关于锁的内容,如下
另外按照不再安全的 OSSpinLock文中所述,如果使用不同优先级别的并发队列,自旋锁是不安全的。
- (void)test {
self.testString = @"1";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"string is %@",self.testString);
sleep(3);
NSLog(@"string is %@",self.testString);
});
sleep(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.testString = @"2";
});
}
输出
string is 1
string is 2
在异步线程中字符串被“意外”修改,如果不希望线程处理过程中 testString 被修改,可以使用加锁的方式处理
#import <libkern/OSAtomic.h>
- (void)test {
OSSpinLock spinlock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&spinlock);
NSLog(@"string is %@",self.testString);
sleep(3);
NSLog(@"string is %@",self.testString);
OSSpinLockUnlock(&spinlock);
});
sleep(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
OSSpinLockLock(&spinlock);
self.testString = @"2";
OSSpinLockUnlock(&spinlock);
});
OSSpinLockLock 起不到作用输出依然是
string is 1
string is 2
替换为 pthread_mutex
#import <pthread.h>
@interface ViewController () {
pthread_mutex_t mutex;
}
- (void)test {
self.testString = @"1";
pthread_mutex_init(&mutex, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
NSLog(@"string is %@",self.testString);
sleep(3);
NSLog(@"string is %@",self.testString);
pthread_mutex_unlock(&mutex);
});
sleep(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
pthread_mutex_lock(&mutex);
self.testString = @"2";
pthread_mutex_unlock(&mutex);
});
}
- (void)dealloc {
pthread_mutex_destroy(&mutex);
}
按照希望的输出
string is 1
string is 1
更多关于多线程和锁的内容点击这里
上述效果除了使用锁的方式实现,还可使用如下方法实现
- (void)test {
self.testString = @"1";
__weak __typeof(self.testString) weakString = self.testString;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(weakString) strongString = weakString;
if(strongString) {
NSLog(@"string is %@",strongString);
sleep(3);
NSLog(@"string is %@",strongString);
}
});
sleep(1);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.testString = @"2";
});
}
dispatch_set_target_queue 控制队列
由于自定义的队列在创建的时候不能指定优先级,它的优先级是默认的,通过这个方式可以设置其优先级。
// 第一个参数为要设置优先级的 queue,第二个参数是参照物,既将第一个 queue 的优先级和第二个queue的优先级设置一样。
dispatch_set_target_queue(serialQ, globalQ);
需要注意的是第一个参数 serialQ 是自定义的队列,不能是系统的队列(global / main),因为不能给系统的队列设置权限。通过上面的设置,serialQ 和 globalQ 一样的优先级。
如果异步执行几个串行队列,但是一个串行队列的目标队列被设置到其他串行队列的上,那么就失去了异步的意义。 如下
dispatch_queue_t targetQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("com.example.gcd.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"blk1 in");
[NSThread sleepForTimeInterval:3.f];
NSLog(@"blk1 out");
});
dispatch_async(queue2, ^{
NSLog(@"blk2 in");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"blk2 out");
});
结果输出
blk1 in
blk1 out
blk2 in
blk2 out
dispatch_after 延迟执行
有时候我们需要延迟一段时间后开始一个队列,例如等几秒钟然后做个动画或者给个提示
// “ull” is a C language literal specifying a type, is for “unsigned long long” type.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds.");
});
注意:这里不是在规定时间后就执行,因为主分发队列是在主线程的 RunLoop 中,例如如果 RunLoop 是 1/60 秒间隔,那么 Block 会在 3 + 1/60 秒后执行
dispatch_group_t 组任务
允许我们监听一组任务是否完成
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(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
dispatch_release(group);
输出
blk1
blk2
blk0
done
上面的结果由于是异步执行所以顺序不一定,但是肯定会是最终才输出done或者说同步地等待一段时间看是否结束:
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(@"blk0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"blk2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
上面是等待所有的队列都执行完,下面这种方式可以等待一段时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
// All the tasks that are associated with the dispatch group are finished
} else {
//some tasks that are associated with the dispatch group are still running.
}
group 的异步线程如果嵌套另一个异步任务,要使用 dispatch_group_enter 和 dispatch_group_leave 保证所有任务执行完成
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
NSLog(@"start");
dispatch_group_async(group, queue, ^{
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"blk0");
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
输出
start
done
blk0
为了得到预想的结果,将上述代码改为
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
NSLog(@"start");
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.f];
NSLog(@"blk0");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
输出
start
blk0
done
可以看到 dispatch_group_async 相当于 dispatch_group_enter 和 dispatch_group_leave 组合
dispatch_barrier_async 屏障
提交的任务会等它前面的任务执行结束才开始,然后它后面的任务必须等它执行完毕才能开始。例如读写数据库或者文件的操作,读写不能同时进行
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
dispatch_barrier_async(queue, blk_for_writing);
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_async(queue, blk7_for_reading);
dispatch_barrier_async可以保证在同一时间,只有一个task在运行,blk_for_writing 时,其他读的 task 不会执行
使用 dispatch_barrier_async ,该函数只能搭配自定义队列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。
dispatch_barrier_sync 和 dispatch_barrier_async 区别
dispatch_barrier_async 不用执行完内部方法即可返回,dispatch_barrier_sync 需要等到执行完内部方法才会返回, 类似 async 与 sync 的区别
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"blk0");
});
dispatch_async(queue, ^{
NSLog(@"blk1");
});
dispatch_barrier_async(queue, ^{
NSLog(@"block barrier");
});
NSLog("queue 1");
dispatch_async(queue, ^{
NSLog(@"blk2");
});
NSLog("queue 2");
输出
blk1
blk0
block barrier
queue 1
queue 2
blk2
将上述代码的 dispatch_barrier_async 改为 dispatch_barrier_sync
输出
blk1
blk0
queue 1
queue 2
block barrier
blk2
dispatch_apply 执行某个代码片段若干次
dispatch_apply 相当于写了一个 for 循环,会等待 queue 中任务全部执行完毕后才会返回,否则一直等待。多线程将需要进行线程切换,线程同步等操作,会有一定的耗时,当单次任务执行的耗时较短,甚至小于线程切换的耗时时,使用 for 循环进行串行操作性能更优,否则应当使用 dispatch_apply,另外如果循环次数过多,可能会导致死锁或者线程爆炸,这是也应当使用 dispatch_apply。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
输出
4
1
0
3
2
done
上面代码相当于
for (int i = 0; i < 5 ; i++) {
dispatch_async(queue, ^{
NSLog(@"%zu", index);
});
}
dispatch_suspend/dispatch_resume 挂起/重新开始一个队列
dispatch_suspend(queue);
dispatch_resume(queue);
dispatch_queue_set_specific / dispatch_queue_get_specific 设置标识符
用于在线程中查找指定的某一个线程
FMDB 中 给队列设置 specific 后,会在 inDatabase 中获取当前执行的队列是否是自己本身,避免死锁
- (void)inDatabase:(void (^)(FMDatabase *db))block {
/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
* and then check it against self to make sure we're not about to deadlock. */
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
// ..
}
dispatch_once
用来初始化全局的数据,保证整个应用程序生命周期中某段代码只被执行一次,线程安全的
static int initialized = NO;
if (initialized == NO) {
// Initializing
initialized = YES;
}
// With the dispatch_once function, it will be modified as follows.
static dispatch_once_t pred;
dispatch_once(&pred, ^{
// Initializing
});
dispatch_source_set_timer CGD 的 timer
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^() {
// do something
});
dispatch_resume(timer);
// cancel timer
dispatch_source_cancel(timer);
只执行一次的话,不需要定时器可使用 dispatch_after
dispatch_semaphore
//创建信号量,信号量的初值如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
//等待降低信号量,如果信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量,这个函数会使传入的信号量 semaphore 的值加1;
dispatch_semaphore_signal(信号量)
NSThread / NSOperationQueue / GCD
NSThread 的使用
// 创建和启动
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadImage) object:nil];
[thread start];
// 创建并自动启动
[NSThread detachNewThreadSelector:@selector(downLoadImage) toTarget:self withObject:nil];
NSOperation 的使用
// 同步执行
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downLoadImage) object:nil];
[operation start];
// 异步执行
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downLoadImage) object:nil];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
- (void)downLoadImage {
// ... Download
// After done, refresh UI
}
NSOperation 可以调用 start 方法来执行任务,但默认是在当前调用的线程(如果没有创建则是主线程)中同步执行的;如果将 NSOperation 添加到 NSOperationQueue 中,系统会自动异步执行 NSOperation 中的操作,异步方法只需要重写 start 方法,则当你添加 operationQueue 后,系统自动调用 start 方法
对比 GCD 用法
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// downLoadImage
dispatch_async(dispatch_get_main_queue(), ^{
// refresh UI
});
});
区别:
- 【优先级】,NSOperationQueue 可以被设置优先级。我们可以让一个 Operation 依赖于另一个 Operation,这样的话尽管两个 Operation 处于同一个并行队列中,但前者会直到后者执行完毕后再执行;GCD 全局并发队列不能设置具体哪个队列依赖哪个队列,但是可以设置优先级别,
- 【KVO】,NSOperationQueue 支持 KVO,意味着我们可以观察任务的执行状态,如 operations,operationCount,maxConcurrentOperationCount,suspended,name。
- 【取消操作】,NSOperation 可以监听一个 Operation 是否完成或取消,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而 GCD 没法停止已经加入 queue 的 Block(其实是有的,但需要许多复杂的代码)
- 【继承】,我们能够对 NSOperation 进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将 block 任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。