iOS多线程__线程与队列

基本概念

  • 进程:系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程运行在其专用的且受保护的内存中。进程是资源分配的最小单位。

一个开工的工厂

  • 线程:线程中执行任务的基本单位,线程是进程中的实体。一个进程必须有至少一个线程或者是多个线程,用以执行任务。iOS进程启动时自动创建一条默认线程“主线程”。

做工的工人

  • 任务:是该执行的操作,也就是线程中执行的那段代码。

工人要做的事情

  • 队列:是一种特殊的线性表,用于存放任务,采用 FIFO(先进先出)的原则。有两种队列:串行队列和并发队列,以及两个特殊队列:主队列和全局队列。

待做任务列表(有序)

总结:开工的工厂中(进程),工人(线程)按照任务列表(队列)的顺序完成工作(任务)。

队列与同异步

  • 串行队列dispatch_queue_create("queue.name", DISPATCH_QUEUE_SERIAL);每次只有一个任务被执行。让任务一个接着一个地执行。

    不要认为串行队列中的所有任务都在同一个线程中执行,串行队列中的异步任务,可能会开启新线程去执行

  • 并发队列dispatch_queue_create("queue.name", DISPATCH_QUEUE_CONCURRENT);可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)。
  • 主队列dispatch_get_main_queue()特殊的串行队列,主队列的任务都在主线程来执行,专门负责调度主线程度的任务,无法开辟新的线程;主队列+异步:不会立即执行,而是等待主队列中的所有其他除我们添加到主队列的任务都执行完毕之后,才会执行我们添加的任务。
    主队列无法开辟新的线程!
  • 全局队列dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);并发队列。在后面加入了“服务质量”和“调度优先级” 两个参数。
  • 同步函数dispatch_sync
    1、必须等待当前语句执行完毕,才会执行下一条语句;
    2、不会开启新的线程;
    3、在当前线程中执行block的任务。
  • 异步函数dispatch_async
    1、不会等待当前语句执行完毕,就可以执行下一句;
    2、会开启线程执行block的任务,具备开启新线程的能力;
    3、异步是多线程的代名词。

队列与同异步的排列组合

通俗解释

打个比喻,系统是工厂主,线程是工人,任务是工作,队列是工作列表。
一个运行中的工厂,有且必须有一个工人(主线程),一个工作列表(主队列)。
串行队列:工作列表上的工作,只能一件一件做。
并行队列:工作列表上的工作,可以多件同时做。
主队列:这个工作列表上的工作很重要,只能最初那个工人(主线程)做,只能一件一件做。工作列表上本身有任务,必须先做完,才能做新添加的任务。
同步任务:工厂主不允许招新的工人。
异步任务:工厂主会更根据工作量,招募新的工人。

串行队列+同步
工作列表上的工作,只能一件一件做,只有一个工人。不能招募新的工人(因为是同步,不能开辟新线程)。
串行队列+异步
工作列表上的工作,只能一件一件做,有两个工人(主线程+异步开启的线程)。
并行队列+同步
工作列表上的事,可以同时做,但是只有一个工人(主线程),所以还是按顺序执行。
并行队列+异步
工作列表上的事,可以同时做,也可以根据工作量招募新的工人(多线程),多个工人同时做各种任务。
主队列+同步
工作列表上的事请只能按顺序做,但是插入了新的任务。只有一个工人,他面临一个问题,新插入的任务在列表前部,需要先做,但是这个工作列表要求必须先把原来的任务完成才能做临时插入的任务。这是就出现了矛盾(死锁崩溃)。
主队列+异步
因为这个工作列表的事情很重要,所以不放心其他的工人操作,所以厂长不会招募新的工人,还是一个工人,按顺序执行。

串行队列并发队列主队列
同步①没有开启新线程,串行执行任务③没有开启新线程,串行执行任务⑤死锁崩溃
异步②有开启新线程(1条),串行执行任务④有开启新线程,并发执行任务⑥没有开启新线程,串行执行任务
①串行队列+同步

不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

/**
 * 同步执行 + 串行队列
 * 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
 */
 * (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncSerial---end");
}

输出结果为:
2019-08-08 14:39:31.366815+0800 YSC-GCD-demo[17285:4197645] currentThread—<NSThread: 0x600001b5e940>{number = 1, name = main}
2019-08-08 14:39:31.366952+0800 YSC-GCD-demo[17285:4197645] syncSerial—begin
2019-08-08 14:39:33.368256+0800 YSC-GCD-demo[17285:4197645] 1—<NSThread: 0x600001b5e940>{number = 1, name = main}
2019-08-08 14:39:35.369661+0800 YSC-GCD-demo[17285:4197645] 2—<NSThread: 0x600001b5e940>{number = 1, name = main}
2019-08-08 14:39:37.370991+0800 YSC-GCD-demo[17285:4197645] 3—<NSThread: 0x600001b5e940>{number = 1, name = main}
2019-08-08 14:39:37.371192+0800 YSC-GCD-demo[17285:4197645] syncSerial—end

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。

  • 所有任务都在打印的
    syncConcurrent---beginsyncConcurrent---end 之间执行(同步任务
    需要等待队列的任务执行结束)。

  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

②串行队列+异步

会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务

/**
 * 异步执行 + 串行队列
 * 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
 */
 * (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"asyncSerial---end");
}

输出结果为:
2019-08-08 14:40:53.944502+0800 YSC-GCD-demo[17313:4203018] currentThread—<NSThread: 0x6000015da940>{number = 1, name = main}
2019-08-08 14:40:53.944615+0800 YSC-GCD-demo[17313:4203018] asyncSerial—begin
2019-08-08 14:40:53.944710+0800 YSC-GCD-demo[17313:4203018] asyncSerial—end
2019-08-08 14:40:55.947709+0800 YSC-GCD-demo[17313:4203079] 1—<NSThread: 0x6000015a0840>{number = 3, name = (null)}
2019-08-08 14:40:57.952453+0800 YSC-GCD-demo[17313:4203079] 2—<NSThread: 0x6000015a0840>{number = 3, name = (null)}
2019-08-08 14:40:59.952943+0800 YSC-GCD-demo[17313:4203079] 3—<NSThread: 0x6000015a0840>{number = 3, name = (null)}

  • 开启了一条新线程(异步执行 具备开启新线程的能力,串行队列 只开启一个线程)。
  • 所有任务是在打印的
    syncConcurrent---beginsyncConcurrent---end 之后才开始执行的(异步执行
    不会做任何等待,可以继续执行任务)。
  • 任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。
③并发队列+同步

在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。

/**
 * 同步执行 + 并发队列
 * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
 */
 * (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncConcurrent---end");
}

输出结果:
2019-08-08 14:32:53.542816+0800 YSC-GCD-demo[16332:4171500] currentThread—<NSThread: 0x600002326940>{number = 1, name = main}
2019-08-08 14:32:53.542964+0800 YSC-GCD-demo[16332:4171500] syncConcurrent—begin
2019-08-08 14:32:55.544329+0800 YSC-GCD-demo[16332:4171500] 1—<NSThread: 0x600002326940>{number = 1, name = main}
2019-08-08 14:32:57.545779+0800 YSC-GCD-demo[16332:4171500] 2—<NSThread: 0x600002326940>{number = 1, name = main}
2019-08-08 14:32:59.547154+0800 YSC-GCD-demo[16332:4171500] 3—<NSThread: 0x600002326940>{number = 1, name = main}
2019-08-08 14:32:59.547365+0800 YSC-GCD-demo[16332:4171500] syncConcurrent—end

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。

  • 所有任务都在打印的
    syncConcurrent—begin 和 syncConcurrent—end 之间执行的(同步任务
    需要等待队列的任务执行结束)。

  • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列
    可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务
    不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务
    需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

④并发队列+异步

可以开启多个线程,任务交替(同时)执行。

/**
 * 异步执行 + 并发队列
 * 特点:可以开启多个线程,任务交替(同时)执行。
 */
 * (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"asyncConcurrent---end");
}

输出结果:
2019-08-08 14:36:37.747966+0800 YSC-GCD-demo[17232:4187114] currentThread—<NSThread: 0x60000206d380>{number = 1, name = main}
2019-08-08 14:36:37.748150+0800 YSC-GCD-demo[17232:4187114] asyncConcurrent—begin
2019-08-08 14:36:37.748279+0800 YSC-GCD-demo[17232:4187114] asyncConcurrent—end
2019-08-08 14:36:39.752523+0800 YSC-GCD-demo[17232:4187204] 2—<NSThread: 0x600002010980>{number = 3, name = (null)}
2019-08-08 14:36:39.752527+0800 YSC-GCD-demo[17232:4187202] 3—<NSThread: 0x600002018480>{number = 5, name = (null)}
2019-08-08 14:36:39.752527+0800 YSC-GCD-demo[17232:4187203] 1—<NSThread: 0x600002023400>{number = 4, name = (null)}

  • 除了当前线程(主线程),系统又开启了 3 个线程,并且任务是交替/同时执行的。(异步执行 具备开启新线程的能力。且 并发队列
    可开启多个线程,同时执行多个任务)。
  • 所有任务是在打印的 syncConcurrent—begin 和
    syncConcurrent—end 之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行
    不做等待,可以继续执行任务)。
⑤主队列+同步

同步执行 + 主队列 在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会。
在主线程中调用 『同步执行 + 主队列』
互相等待卡住不可行

/**
 * 同步执行 + 主队列
 * 特点(主线程调用):互等卡主不执行。
 * 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncMain {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncMain---end");
}

输出结果
2019-08-08 14:43:58.062376+0800 YSC-GCD-demo[17371:4213562] currentThread—<NSThread: 0x6000026e2940>{number = 1, name = main}
2019-08-08 14:43:58.062518+0800 YSC-GCD-demo[17371:4213562] syncMain—begin
(lldb)

在主线程中使用 同步执行 + 主队列 可以惊奇的发现:

追加到主线程的任务 1、任务 2、任务 3 都不再执行了,而且 syncMain---end 也没有打印,在 XCode 9 及以上版本上还会直接报崩溃。这是为什么呢?
这是因为我们在主线程中执行 syncMain 方法,相当于把 syncMain 任务放到了主线程的队列中。而 同步执行 会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把 任务 1 追加到主队列中,任务 1 就在等待主线程处理完 syncMain 任务。而syncMain 任务需要等待 任务 1 执行完毕,才能接着执行。

那么,现在的情况就是 syncMain 任务和 任务 1 都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且 syncMain—end 也没有打印。

在其他线程中调用 『同步执行 + 主队列』
不会开启新线程,执行完一个任务,再执行下一个任务

// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

输出结果:
2019-08-08 14:51:38.137978+0800 YSC-GCD-demo[17482:4237818] currentThread—<NSThread: 0x600001dd6c00>{number = 3, name = (null)}
2019-08-08 14:51:38.138159+0800 YSC-GCD-demo[17482:4237818] syncMain—begin
2019-08-08 14:51:40.149065+0800 YSC-GCD-demo[17482:4237594] 1—<NSThread: 0x600001d8d380>{number = 1, name = main}
2019-08-08 14:51:42.151104+0800 YSC-GCD-demo[17482:4237594] 2—<NSThread: 0x600001d8d380>{number = 1, name = main}
2019-08-08 14:51:44.152583+0800 YSC-GCD-demo[17482:4237594] 3—<NSThread: 0x600001d8d380>{number = 1, name = main}
2019-08-08 14:51:44.152767+0800 YSC-GCD-demo[17482:4237818] syncMain—end

  • 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。

  • 所有任务都在打印的
    syncConcurrent---beginsyncConcurrent---end 之间执行(同步任务
    需要等待队列的任务执行结束)。

  • 任务是按顺序执行的(主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

为什么现在就不会卡住了呢?

因为syncMain 任务 放到了其他线程里,而 任务 1、任务 2、任务3 都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务 在其他线程中执行到追加 任务 1 到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的 任务1,等 任务1 执行完毕,再接着执行 任务 2、任务 3。所以这里不会卡住线程,也就不会造成死锁问题。

⑥主队列+异步

只在主线程中执行任务,执行完一个任务,再执行下一个任务。

/**
 * 异步执行 + 主队列
 * 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
 * (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"asyncMain---end");
}

输出结果:
2019-08-08 14:53:27.023091+0800 YSC-GCD-demo[17521:4243690] currentThread—<NSThread: 0x6000022a1380>{number = 1, name = main}
2019-08-08 14:53:27.023247+0800 YSC-GCD-demo[17521:4243690] asyncMain—begin
2019-08-08 14:53:27.023399+0800 YSC-GCD-demo[17521:4243690] asyncMain—end
2019-08-08 14:53:29.035565+0800 YSC-GCD-demo[17521:4243690] 1—<NSThread: 0x6000022a1380>{number = 1, name = main}
2019-08-08 14:53:31.036565+0800 YSC-GCD-demo[17521:4243690] 2—<NSThread: 0x6000022a1380>{number = 1, name = main}
2019-08-08 14:53:33.037092+0800 YSC-GCD-demo[17521:4243690] 3—<NSThread: 0x6000022a1380>{number = 1, name = main}

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然 异步执行

  • 具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。
    所有任务是在打印的 syncConcurrent---begin
    syncConcurrent---end 之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。

  • 任务是按顺序执行的(因为主队列是
    串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

这就是基本地组合,队列嵌套的情况也就是这些简单组合的叠加。

主线程与主队列的关系

主队列为什么不能开辟新线程?
主要是为了确保UI任务的性能和安全性,因为所有的UI任务都是放在主线程里进行的,本身OC是个线程不安全的语言,所以强制只能让主线程执行UI任务,否则就会出现:
UI不一致,因为并发导致的更新顺序不确定,界面展示错误;
数据被多方持有,同时修改;

所以主队列不能开辟新的线程,主队列中的所有任务必须在主线程中执行。

主线程能不能执行别的队列中的任务?
可以,为了避免性能消耗,减少线程切换。在主线程闲置的时候会执行别的队了中的任务。

参考资料:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值