iOS多线程-基本认识

/ 0评 / 0

iOS中的常见多线程方案

容易混淆的术语

有 4 个术语比较容易混淆:同步异步并发串行

同步异步主要影响:能不能开启新的线程

并发串行主要影响:任务的执行方式

GCD的常用函数

GCD 源码:https://github.com/apple/swift-corelibs-libdispatch

同步 vs 异步

同步和异步操作的主要区别在于 是否等待操作执行完成,亦即是否阻塞当前线程。

同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。

同步 的方式执行任务:

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)

异步 的方式执行任务:

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block)

并发 vs 串行

从本质上来说,串行和并发的主要区别在于 允许同时执行的任务数量

并发队列Concurrent Dispatch Queue

串行队列Serial Dispatch Queue

GCD 创建队列:

// DISPATCH_QUEUE_SERIAL 串行队列
// DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_t queue = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);

各种队列的执行效果

死锁

使用 sync 函数往 当前串行队列 中添加任务,会卡住当前的串行队列(产生死锁)。

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t serial = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial, ^{
        // 死锁
        dispatch_sync(serial, ^{
            NSLog(@"任务 - %@", [NSThread currentThread]);
        });
    });

    // 死锁
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任务 - %@", [NSThread currentThread]);
    });
}

sync 的特点:立马 在当前线程执行任务,执行完毕才能继续往下执行。

串行队列 的特点:排队执行,先进先出(FIFO)。

这样就会导致当前没有没有执行完,又希望执行新的任务,就会卡住。

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0):全局队列是并发队列,有高、默认、低和后台 4 个优先级。相同优先级的队列获取多次,获得的是同一个队列

dispatch_get_main_queue():主队列是串行队列,主队列中的任务都是在主线程中依次执行的。

子线程中的定时器

afterDelay 的函数底层使用了定时器,定时器是需要添加到 RunLoop 里面去的。子线程默认没有启动 RunLoop,所以定时器没法工作。

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
    });
    // 打印顺序: 1 3
}
- (void)test
{
    NSLog(@"2");
}

RunLoop 执行定时器是在 被某个消息唤醒时 执行的,并不是立马执行。

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"1");
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"3");
    // 打印顺序: 1 3 2
}
- (void)test
{
    NSLog(@"2");
}

让子线程中的定时器生效:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
    // 打印顺序: 1 3 2
}

- (void)test
{
    NSLog(@"2");
}

GNUstep

苹果没有开源 Foundation 框架源码,但在 GNU 计划的 GNUstep 项目中,将 CocoaOC 库重新开源实现了一遍。虽然 GNUstep 不是苹果官方源码,但还是具有一定的参考价值。

下载地址:http://wwwmain.gnustep.org/resources/downloads.php

可以在 NSRunLoop.m 中找到下面代码,可以证实我们上面的结论:

- (void) performSelector: (SEL)aSelector
          withObject: (id)argument
          afterDelay: (NSTimeInterval)seconds
{
  NSRunLoop     *loop = [NSRunLoop currentRunLoop];
  GSTimedPerformer  *item;

  item = [[GSTimedPerformer alloc] initWithSelector: aSelector
                         target: self
                       argument: argument
                          delay: seconds];
  [[loop _timedPerformers] addObject: item];
  RELEASE(item);
  [loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}

子线程自动退出

和上面的问题类似,子线程默认没有启动 RunLoop,所以线程任务执行完毕后自动退出。比如下面代码,打印 1 后程序崩溃:

- (void)viewDidLoad {
    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
    }];
    [thread start];

    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)test
{
    NSLog(@"2");
}

在子线程中启动 RunLoop,即可正常打印 1 2

- (void)viewDidLoad {
    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
        // 子线程休眠,等待消息
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];

    // 发送消息,唤醒子线程,执行任务
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)test
{
    NSLog(@"2");
}

队列组

队列组可以实现让任务分组执行,比如下面代码实现了 子线程 并发执行 任务1和任务2,然后再回到主线程执行任务3

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务1");
    }
});

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务2");
    }
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"任务3");
});

下面代码实现了 子线程 并发执行 任务1和任务2,然后再执行任务3和任务4

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务1");
    }
});

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务2");
    }
});

dispatch_group_notify(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务3");
    }
});

dispatch_group_notify(group, queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"任务4");
    }
});

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注