GCD死锁之你过来啊

死锁

  • 概念

  1. 百度百科:两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
  2. 水手理解:线程中存在两个执行的任务A和B,任务A需要任务B执行完成才能往下执行,任务B需要任务A完成执行并返回也才能往下执行,所以导致两个任务互相等待使程序无法继续执行,称之为死锁!!

最简单的死锁

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"block"); // 任务1
    });
    NSLog(@"mjz"); // 任务2
}
  1. 在主线程上,任务2会先加入主线程队列中,再将任务1也加入进主线程队列中,即任务1在任务2之后加入队列;
  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);
}
  • 自定义串行队列若队列是新创建自定义的串行队列,则无执行任务、挂起、加锁等操作,直接执行任务并返回;
  • 主队列
  1. 队列为主队列时,若当前获取的主队列有其它任务执行、挂起、加锁等操作,dispatch_semaphore_wait函数是使队列的任务在当前dispatch_sync所属队列中进行信号量等待,使信号量减1,当前线程阻塞;_dispatch_queue_push是将当前队列的任务push提交到主队列中;因为此时队列执行dispatch_semaphore_wait函数信号量等待中,导致主队列阻塞,所以将导致block任务不执行,同时dispatch_semaphore_signal发送信号量也不被执行;
  2. 队列为主队列时,若当前获取的主队列无任务执行、挂起、加锁等操作,则任务直接执行,执行完之后函数返回;

?自定义串行队列

- (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执行:

  1. dispatch_semaphore_wait会阻塞当前dispatch_sync所属的任务队列,即当前为主队里;
  2. _dispatch_queue_push会将任务block push提交到dq队列,即dispatch_get_main_queue中;
  3. 由于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:
  1. 当并发队列中有任务在执行时,则直接进入 _dispatch_sync_f_slow 函数,需等待任务执行完。再执行当前任务block;
  2. 如果不存在其他任务,会判断这个任务是用户自创建的队列还是全局队列,如果是用户自己创建的队列,直接执行任务;如果是全局队列,则唤醒这个队列,进入 _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执行:

  1. 在新的全局并发队列globalQueue中,dispatch_semaphore_wait会阻塞当前线程执行block任务;
  2. 直到任务执行完成,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同样的执行道理:

  1. dispatch_semaphore_wait会阻塞当前dispatch_sync所属的任务队列,即当前为串行队列;
  2. _dispatch_queue_push会将任务block push提交到queue队列,即串行队列serial中;
  3. 由于dispatch_semaphore_wait使得串行队列线程已经阻塞,所以导致push到queue队列中的block任务无法执行,导致dispatch_semaphore_signal无法执行,造成死锁!

执行任务分析:

  1. 任务1第一个被执行;
  2. 由于dispatch_async执行函数立马返回,所以在串行队列中任务5被执行了;
  3. 接下去任务2在dispatch_async中被执行;
  4. 由于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在串行队列下嵌套会导致死锁!!

 

总结

  1. 在当前串行队列下同步添加block任务会导致死锁;
  2. 在串行队列中添加任务使用dispatch_async方法;
  3. 合理地使用GCD方法,在适当合理地位置上多加个a;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值