OC-主线程调dispatch_semaphore_wait的坑,线程会卡死

我们都知道线程同步方案中dispatch_semaphore是一个很不错的选择,但是dispatch_semaphore如果在主线程中等待,另外开启线程再回到主线程释放信号,就会出现信号卡死的情况。

信号量的使用

简单回顾一下信号量的使用,API如下:

// 创建信号量,参数:信号量的初值,表示最多几个资源可访问。
dispatch_semaphore_create(信号量值)
//等待信号量
dispatch_semaphore_wait(信号量,等待时间)
//发送信号量
dispatch_semaphore_signal(信号量)

1、执行dispatch_semaphore_create 会根据传入的long型参数创建对应数目的信号量;这个也是可以控制并发线程的数量;
2、执行dispatch_semaphore_signal 会增加一个信号量;
3、执行dispatch_semaphore_wait ,如果信号量<=0, 就行进入休眠等待;如果> 0, 则会减少一个信号量, 并执行后面的代码;
4、正常的使用顺序是先wait然后再signal,这两个函数通常成对使用。 
5、如果信号量是0,就会根据传入的等待时间来等待。

信号量简单使用的demo:

- (void)viewDidLoad {
    [super viewDidLoad];

    if ([self testSemaphore]) {
        NSLog(@"执行了信号量的释放操作");
    }else {
        NSLog(@"未执行信号量的释放操作");
    }
}

-  (BOOL)testSemaphore {
    __block BOOL resultValue = NO;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"创建信号量");
    [self semaphoreBlock:^{
        resultValue = YES;
        NSLog(@"开始释放信号量,%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
        NSLog(@"结束释放信号量,%@", [NSThread currentThread]);
    }];
    NSLog(@"等待信号量,%@", [NSThread currentThread]);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"执行returen操作啦");
    return resultValue;
}


- (void)semaphoreBlock:(void(^)(void))block {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"进来block了,%@", [NSThread currentThread]);
        block();
    });
}

执行代码查看结果如下:
OC-主线程调dispatch_semaphore_wait的坑,线程会卡死_第1张图片
可以看到是执行了信号量释放的结果。

信号量阻塞线程的情况

就以上一份代码为例,我们修改一下,把semaphoreBlock:(void(^)(void))block这个方法里面的dispatch_get_global_queue替换成dispatch_get_main_queue的话,那么线程就会卡死,代码如下:

- (void)semaphoreBlock:(void(^)(void))block {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"进来block了,%@", [NSThread currentThread]);
        block();
    });
}

执行代码查看结果如下:
OC-主线程调dispatch_semaphore_wait的坑,线程会卡死_第2张图片
执行的结果可以看出,线程被卡死了~~

信号量阻塞线程的分析

  • 创建的总信号量为0,代码执行到dispatch_semaphore_wait时, 主线程阻塞,直到收到信号才会往下继续执行
  • dispatch_semaphore_signal(s)发送信号是:开启了另外一个线程后回到主线程中执行,由于此时主线程是阻塞的,那么dispatch_semaphore_signal(s)不会执行,这形成了死锁的情况。
  • (等于说,发送信号对于主线程来说是另外一个任务,而且这个任务在testSemaphore任务的后面;但是在主线程时串行队列,需要先执行完testSemaphore这个任务,才能执行dispatch_semaphore_signal(s)这个任务;而要执行testSemaphore这个任务,又需要执行dispatch_semaphore_signal(s)这个任务; 所以两个任务相互等待,就死锁了)。
  • 解决方案:要么把整个任务都放到子线程中,要么信号量的释放不在主线程中释放

信号量阻塞线程的解决方法

1.任务放在子线程中

代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        if ([self testSemaphore]) {
            NSLog(@"执行了信号量的释放操作");
        }else {
            NSLog(@"未执行信号量的释放操作");
        }
    });
}

-  (BOOL)testSemaphore {
    __block BOOL resultValue = NO;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"创建信号量");
    [self semaphoreBlock:^{
        resultValue = YES;
        NSLog(@"开始释放信号量,%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
        NSLog(@"结束释放信号量,%@", [NSThread currentThread]);
    }];
    NSLog(@"等待信号量,%@", [NSThread currentThread]);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"执行returen操作啦");
    return resultValue;
}

- (void)semaphoreBlock:(void(^)(void))block {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"进来block了,%@", [NSThread currentThread]);
        block();
    });
}

2.信号释放不在主线程中执行

代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    if ([self testSemaphore]) {
        NSLog(@"执行了信号量的释放操作");
    }else {
        NSLog(@"未执行信号量的释放操作");
    }
}

-  (BOOL)testSemaphore {
    __block BOOL resultValue = NO;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"创建信号量");
    [self semaphoreBlock:^{
        resultValue = YES;
        NSLog(@"开始释放信号量,%@", [NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
        NSLog(@"结束释放信号量,%@", [NSThread currentThread]);
    }];
    NSLog(@"等待信号量,%@", [NSThread currentThread]);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"执行returen操作啦");
    return resultValue;
}

- (void)semaphoreBlock:(void(^)(void))block {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"进来block了,%@", [NSThread currentThread]);
        block();
    });
}

总结

一般我们在使用第三方时,例如下载图片或者其他耗时的操作,会在子线程中操作,然后再在主线程回调,暴露给开发者处理刷新或者其他操作;如果此时在主线程回调中处理释放信号,那么就会可能出现卡死的情况。所以当我们不能控制接口调用线程时,最好不要在的当前线程永久等待信号量。
所以,一般当我们用第三方中使用信号量出现卡死的情况时,大部分的原因是因为他们的暴露的方法的回调是在主线中执行的~~~

你可能感兴趣的:(OC学习小记)