死锁
-
概念
- 百度百科:两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
- 水手理解:线程中存在两个执行的任务A和B,任务A需要任务B执行完成才能往下执行,任务B需要任务A完成执行并返回也才能往下执行,所以导致两个任务互相等待使程序无法继续执行,称之为死锁!!
最简单的死锁
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"block"); // 任务1
});
NSLog(@"mjz"); // 任务2
}
- 在主线程上,任务2会先加入主线程队列中,再将任务1也加入进主线程队列中,即任务1在任务2之后加入队列;
- 任务2必须等任务1执行完成并返回后才能往下执行,而由于在队列中任务2在任务1前面,所以任务1的执行需要等任务2完成后也才可以往下执行,所以互相等待导致了死锁!!
同步 (dispatch_sync)
- 同步执行会将block任务提交到队列中,并等待当前任务执行完成后函数才能返回。所以在任务执行过程中,当前线程处于阻塞状态!
异步 (dispatch_async)
- 异步执行则是开启新的线程执行队列,当在并行队列中,将开启多个线程并发执行队列,若执行地队列中线程已在执行任务,则其它任务等待正在执行的任务执行完,才能执行当前剩余地任务(开启线程数量有限)。而且并行队列中异步执行任务时,任务将无序地执行,最重要的是无需等待任务执行完成,则dispatch_async函数直接返回!!!所以异步不会导致线程死锁!
?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"任务1---%@", [NSThread currentThread]); // 任务1
});
NSLog(@"任务2"); // 任务2
}
- dispatch_async异步执行函数立即返回,不阻塞线程,所以不会造成死锁!!
?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"任务1---%@", [NSThread currentThread]); // 任务1
});
NSLog(@"任务2"); // 任务2
}
- 在viewDidLoad中主队列执行dispatch_sync的block任务将导致死锁!!
源码分析
1、向队列同步的添加任务block
void
dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
if (dq->dq_width == 1) {
return dispatch_barrier_sync_f(dq, ctxt, func);
}
// ......
}
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
if (slowpath(dq->dq_items_tail)
|| slowpath(DISPATCH_OBJECT_SUSPENDED(dq))
|| slowpath(!_dispatch_queue_trylock(dq))) {
return _dispatch_barrier_sync_f_slow(dq, ctxt, func);
}
func(ctxt);
}
static void
_dispatch_barrier_sync_f_slow(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
struct dispatch_barrier_sync_slow2_s dbss2 = {
.dbss2_dq = dq,
#if DISPATCH_COCOA_COMPAT
.dbss2_func = func,
.dbss2_ctxt = ctxt,
#endif
.dbss2_sema = _dispatch_get_thread_semaphore(),
};
struct dispatch_barrier_sync_slow_s {
DISPATCH_CONTINUATION_HEADER(dispatch_barrier_sync_slow_s);
} dbss = {
.dc_func = _dispatch_barrier_sync_f_slow_invoke,
.dc_ctxt = &dbss2,
};
_dispatch_queue_push(dq, (void *)&dbss);
dispatch_semaphore_wait(dbss2.dbss2_sema, DISPATCH_TIME_FOREVER);
}
- 自定义串行队列:若队列是新创建自定义的串行队列,则无执行任务、挂起、加锁等操作,直接执行任务并返回;
- 主队列:
- 队列为主队列时,若当前获取的主队列有其它任务执行、挂起、加锁等操作,dispatch_semaphore_wait函数是使队列的任务在当前dispatch_sync所属队列中进行信号量等待,使信号量减1,当前线程阻塞;_dispatch_queue_push是将当前队列的任务push提交到主队列中;因为此时队列执行dispatch_semaphore_wait函数信号量等待中,导致主队列阻塞,所以将导致block任务不执行,同时dispatch_semaphore_signal发送信号量也不被执行;
- 队列为主队列时,若当前获取的主队列无任务执行、挂起、加锁等操作,则任务直接执行,执行完之后函数返回;
?自定义串行队列
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t serialQueue = dispatch_queue_create("com.mjz.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"任务1");
});
}
- 由于生成的serialQueue队列为自定义队列,无其它执行任务,所以提交到serialQueue中的block任务被执行并返回;
? 主队列1的情况
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1"); // 任务1
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"任务2---%@", [NSThread currentThread]); // 任务2
});
NSLog(@"任务3"); // 任务3
}
dispatch_sync执行:
- dispatch_semaphore_wait会阻塞当前dispatch_sync所属的任务队列,即当前为主队里;
- _dispatch_queue_push会将任务block push提交到dq队列,即dispatch_get_main_queue中;
- 由于dispatch_semaphore_wait使得主线程已经阻塞,所以导致push到dq队列中的block任务无法执行,导致dispatch_semaphore_signal无法执行,造成死锁!
?主队列2的情况
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t serialQueue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(serialQueue, ^{
NSLog(@"任务1");
dispatch_sync(mainQueue, ^{
NSLog(@"任务2");
});
});
}
- dispatch_async重新开启新的线程执行任务,dispatch_sync中主队列无其它任务,所以直接执行并返回;
2、并行队列同步的添加block
static void
_dispatch_sync_f_slow(dispatch_queue_t dq)
{
// the global root queues do not need strict ordering
if (dq->do_targetq == NULL) {
dispatch_atomic_add(&dq->dq_running, 2);
return;
}
struct dispatch_sync_slow_s {
DISPATCH_CONTINUATION_HEADER(dispatch_sync_slow_s);
} dss = {
.do_vtable = NULL,
.dc_func = _dispatch_sync_f_slow2,
.dc_ctxt = _dispatch_get_thread_semaphore(),
};
// XXX FIXME -- concurrent queues can be come serial again
_dispatch_queue_push(dq, (void *)&dss);
dispatch_semaphore_wait(dss.dc_ctxt, DISPATCH_TIME_FOREVER);
_dispatch_put_thread_semaphore(dss.dc_ctxt);
}
static void
_dispatch_sync_f_slow2(void *ctxt)
{
dispatch_queue_t dq = _dispatch_queue_get_current();
dispatch_atomic_add(&dq->dq_running, 2);
dispatch_semaphore_signal(ctxt);
}
- 并发队列宽度大于1:
- 当并发队列中有任务在执行时,则直接进入 _dispatch_sync_f_slow 函数,需等待任务执行完。再执行当前任务block;
- 如果不存在其他任务,会判断这个任务是用户自创建的队列还是全局队列,如果是用户自己创建的队列,直接执行任务;如果是全局队列,则唤醒这个队列,进入 _dispatch_sync_f_slow 函数,这个函数是会将信号量压进它的形参队列中,然后等待这个信号量,若是全局队列则很快执行并返回这个信号量,随后直接执行任务并返回;
?
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
NSLog(@"任务1---%@", [NSThread currentThread]); // 任务1
dispatch_sync(globalQueue, ^{
NSLog(@"任务2----%@", [NSThread currentThread]); // 任务2
dispatch_sync(globalQueue, ^{
NSLog(@"任务4----%@", [NSThread currentThread]); // 任务4
});
});
NSLog(@"任务3----%@", [NSThread currentThread]); // 任务3
});
// 执行结果:
// 任务1---<NSThread: 0x600003f3cac0>{number = 3, name = (null)}
// 任务2----<NSThread: 0x600003f3cac0>{number = 3, name = (null)}
// 任务4----<NSThread: 0x600003f3cac0>{number = 3, name = (null)}
// 任务3----<NSThread: 0x600003f3cac0>{number = 3, name = (null)}
- 在并发队列中同步的添加任务不会造成死锁!
趁热打铁
?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1"); // 任务1
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_sync(globalQueue, ^{
NSLog(@"任务2---%@", [NSThread currentThread]); // 任务2
});
NSLog(@"任务3"); // 任务3
}
dispatch_sync执行:
- 在新的全局并发队列globalQueue中,dispatch_semaphore_wait会阻塞当前线程执行block任务;
- 直到任务执行完成,dispatch_semaphore_signal发送信号量使线程不阻塞,程序继续往下执行!
?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("com.mjz.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"任务1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"任务2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"任务3"); // 任务3
});
NSLog(@"任务4"); // 任务4
});
NSLog(@"任务5"); // 任务5
}
// 执行结果
// 任务1
// 任务5
// 任务2
dispatch_sync同样的执行道理:
- dispatch_semaphore_wait会阻塞当前dispatch_sync所属的任务队列,即当前为串行队列;
- _dispatch_queue_push会将任务block push提交到queue队列,即串行队列serial中;
- 由于dispatch_semaphore_wait使得串行队列线程已经阻塞,所以导致push到queue队列中的block任务无法执行,导致dispatch_semaphore_signal无法执行,造成死锁!
执行任务分析:
- 任务1第一个被执行;
- 由于dispatch_async执行函数立马返回,所以在串行队列中任务5被执行了;
- 接下去任务2在dispatch_async中被执行;
- 由于dispatch_sync会阻塞线程造成死锁,所以任务3和任务4不被执行!
?
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任务1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2"); // 任务2
});
NSLog(@"任务3"); // 任务3
});
NSLog(@"任务4"); // 任务4
while (1) {
}
NSLog(@"任务5"); // 任务5
}
// 执行结果:
// 任务1
// 任务4
- 任务1和任务4优先被执行,等待执行到主队列任务时,由于主线程被死循环阻塞,所以程序无法往下执行;
?dispatch_apply
dispatch_queue_t serialQueue = dispatch_queue_create("com.mjz.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(1, serialQueue, ^(size_t index) {
NSLog(@"任务1");
dispatch_apply(1, serialQueue, ^(size_t index) {
NSLog(@"任务2");
});
});
- dispatch_apply在串行队列下嵌套会导致死锁!!
总结
- 在当前串行队列下同步添加block任务会导致死锁;
- 在串行队列中添加任务使用dispatch_async方法;
- 合理地使用GCD方法,在适当合理地位置上多加个a;