线程、队列、任务(同、异步)等概念详解

在那一刻,我意识到,我必须选择,要么对一切屈服,得过且过地生活,要么就得努力,争取过上梦想的生活。

进程、线程和以及程序


现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。有了系统级别的支持,我们应该在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算。可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行。但是机器码是按顺序执行的,一个复杂的多步操作只能一步步按顺序逐个执行。改变这种状况可以从下面讲到的两个角度(单核和多核)出发。
什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。即对于单核处理器,可以将多个步骤放到不同的线程,这样一来用户完成UI操作后其他后续任务在其他线程中,当CPU空闲时会继续执行,而此时对于用户而言可以继续进行其他操作;
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,即使是多核CPU操作系统,操作系统也会自动把很多任务轮流调度到每个核心上执行。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

进程

狭义定义:进程就是一段程序的执行过程。即在系统中正在运行的一个应用程序。比如同时打开微信和QQ,系统会分别启动2个进程;每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

简单的来讲进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

进程模拟.png
线程和主线程

一个进程要想执行任务,必须得有线程(每一个进程至少要有一条线程,即主线程)。
在引入线程的操作系统中,线程是进程中执行运算的最小单位,是进程中的一个实体,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度;一个进程(程序)的所有任务都在线程中执行。
一个程序有且只有一个主线程(UI线程),程序启动时创建(调用main来启动),主线程的生命周期是和应用程序绑定,程序退出时,主线程也停止;由主线程的唯一性和功能特性,我们不要将耗时的操作放到主线程中,耗时操作应放在子线程(后台线程,非主线程); 凡是和UI相关的操作应放在主线程中操作。
同一时间内,一个线程只能执行一个任务,若要在1个进程中执行多个任务,那么只能一个个的按顺序执行这些任务(线程的串行)。
线程自己不拥有系统资源,只拥有在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

线程模拟.png
多线程

概念:一个进程中可以开启多条线程,各线程可以并行(同时)执行不同的任务;
原理:同一时间,一个CPU只能处理一条线程,只有一条线程在工作,多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象;
优点:提高程序资源使用效率来提高系统的效率(CPU、内存利用率);
缺点:创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB,主线程1MB)、创建线程大约需要90毫秒的创建时间,如果开启大量的线程,会降低程序的性能(一般最多3到5个);线程越多,CPU在调度线程上的开销就越大;程序设计更加复杂(比如线程之间的通信、多线程的数据共享);
iOS中多线程的实现方案:

  • pthread
  • NSThread
  • GCD
  • NSOperation
线程的几种状态
  • 创建:新创建一个线程对象;
  • 开启(就绪状态):线程对象创建之后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权;
  • 运行:开启(就绪)状态的线程获取了CPU资源,执行程序代码;
  • 阻塞:因某种原因放弃CPU使用权,暂停运行,直到线程进入就绪状态,才有机会转到运行状态;线程处理阻塞状态时在内存中的表现情况是线程被移出可调度线程池,此时不可调度;
  • 死亡:线程执行完了、因异常退出了run方法或者是强制退出,这三种情况会导致线程的死亡,线程生命周期结束;


    线程状态
程序

说起进程,就不得不说下程序。先看定义:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程则是在处理机上的一次执行过程,它是一个动态的概念。这个不难理解,其实进程是包含程序的,进程的执行离不开程序,进程中的文本区域就是代码区,也就是程序。

进程和线程比较
  • 进程是CPU分配资源和调度的单位;
  • 线程是CPU调度(执行任务)的最小单位,是程序执行的最小单元;
  • 同一个进程内的线程共享进程的所有资源;

任务和队列


概念:

  • 任务(Task):要执行的操作;
  • 队列(Queue):队列是用来管理线程的,队列里面放着很多的任务,来管理这些任务什么时候在那条线程里去执行;广义定义为队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,即FIFO(front input front output),和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列类型:

  • 串行队列(serialQueue):队列中的线程按顺序执行(不会同时执行),即一次只能执行一个task;


  • 并发队列(concurrentQueue):队列中的线程会并发执行(同时执行),即一次可以执行多个task;


  • 主队列:主线程队列只有一个线程(主线程),主队列是串行队列;
队列

任务(同、异步)


  • 同步任务(Synchronous):所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。优先级高,在线程中有执行顺序,不会开启新的线程;
  • 异步任务(Asynchronous):异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程

队列和任务的区分


  1. 同步和异步主要影响:能不能开启新的线程。
    同步:只是在当前线程中执行任务,不具备开启新线程的能力。
    异步:可以在新的线程中执行任务,具备开启新线程的能力。
  2. 并发和串行主要影响:任务的执行方式
    并发:允许多个任务并发(同时)执行。
    串行:一个任务执行完毕后,再执行下一个任务。

以上概念之间关系抽象理解和举例实现


  • 队列(queue)当做是管道容器。
  • 容器(queue)里面可以装有货物(task)。
  • 线程(thread)相当于生产线,多线程相当于有多条生产线。
  • 同步异步相当于货物(task) 在生产线上是否生成完成。
  • 串行队列和并行队列是对货物的处理方式,串行队列相当于只允许一条生产线(thread)处理队列的任务(task),并行队列相当于允许多条生产线(thread)处理队列的任务。

从上面看我们想执行一个任务task ,有两个要素,queue(容器)和对task的处理方式(同步和异步)。(不需要thread,因为queue是自动选择生产线的thread)。因此对任务的处理排列组合有四种情况

序号 任务处理排列组合
1 同步+串行队列
2 异步+串行队列
3 同步+并发队列
4 异步+并发队列
(1)任务处理 同步 + 串行队列的线程选择
void syncSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 创建串行队列 */
    dispatch_queue_t serialQueue1 = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);

    /* 2. 将任务放到队列中 */
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task1 exe thread--------%@",[NSThread currentThread]);
    });

    dispatch_sync(serialQueue2, ^{
        NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue2, ^{
        NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(serialQueue1, ^{
        NSLog(@"serialQueue1 task3 exe thread--------%@",[NSThread currentThread]);
    });
}

执行结果

2019-08-28 22:14:00.994993+0800 任务[797:36742] current thread <NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995158+0800 任务[797:36742] serialQueue1 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995276+0800 任务[797:36742] serialQueue2 task1 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995374+0800 任务[797:36742] serialQueue1 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995473+0800 任务[797:36742] serialQueue2 task2 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}
2019-08-28 22:14:00.995563+0800 任务[797:36742] serialQueue1 task3 exe thread--------<NSThread: 0x6000020294c0>{number = 1, name = main}

同步 + 串行队列

同步执行 + 串行队列 可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行 不具备开启新线程的能力)。
  • 所有任务都是依次打印的 (同步任务 需要等待队列的任务执行结束)。
  • 在当前线程中将队列的任务按照加入的顺序依次执行完毕,即任务是按顺序执行的(串行队列 每次只有一个任务被执行,任务一个接一个按顺序执行)。

串行队列中的特殊队列,主队列MainQueue,那么同步 + 主队列对任务的处理情况会是什么样的呢?

void syncMainQueue() {
    NSLog(@"------start-------");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"download1-------%@",[NSThread currentThread]);
    });
    NSLog(@"-------end---------");
}

执行结果

同步 + 主队列

直接崩溃了, 以前这种情况是会发生死锁的,现在直接报错。 那么, 为什么会发生这种情况呢?这里需要探讨下,什么时候线程和queue绑定在一起了。

void syncMoreSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    //1.创建串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    //2.将任务放到队列里
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
        dispatch_sync(serialQueue, ^{
            NSLog(@"serialQueue task2 exe thread-%@",[NSThread currentThread]);
        });
    });
}

执行结果:
2019-09-02 11:06:34.575166+0800 任务[4420:131455] current thread <NSThread: 0x6000038348c0>{number = 1, name = main}
2019-09-02 11:06:34.575421+0800 任务[4420:131455] serialQueue task1 exe thread-<NSThread: 0x6000038348c0>{number = 1, name = main}

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

同样发生了崩溃。
我们发现在主线程中调用同步sync调用串行队列serialQueue ,没有发生崩溃。而在串行队列的task中再次调用 同步sync调用串行队列serialQueue执行任务发生崩溃了,但是串行队列的task执行是在主线程中的。为什么第一次调用主队列调用task没问题,而第二次执行执行task发生崩溃了呢?

syncMoreSerial

我们发现在执行task1 的时候,在serierQueue中添加了task2,同时要求执行task2,这个时候,task1还没有完成,这违背了串行队列的含义了,FIFO,崩溃了。

主线程也是同样的道理,可以这样理解, 上图中, 执行syncMainQueue这个方法是在主线程中执行的, 你可以把它看做一个任务A, 这个任务A也是在主队列中的,那么代码执行到第一个dispatch_sync的时候, 启动了任务B, 把任务B放进了主队列中, 由于是同步执行, 所以, 必须等待任务B执行完了之后才能继续向下执行, 但是主线程有任务A, 所以任务B无法放到主线程中去执行,任务B等待任务A执行, 任务A等待任务B执行, 这样就造成了死锁。

同步 + 主队列 = 死锁

我们再来看个例子:

void syncMoreSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    //1.创建串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue1 = dispatch_queue_create("serialQueue1", DISPATCH_QUEUE_SERIAL);
    //2.将任务放到队列里
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread-%@",[NSThread currentThread]);
        dispatch_sync(serialQueue1, ^{
            NSLog(@"serialQueue1 task1 exe thread-%@",[NSThread currentThread]);
        });
    });
}

执行结果:
2019-09-02 11:13:56.558896+0800 任务[4486:142175] current thread <NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559102+0800 任务[4486:142175] serialQueue task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}
2019-09-02 11:13:56.559211+0800 任务[4486:142175] serialQueue1 task1 exe thread-<NSThread: 0x600001816900>{number = 1, name = main}

上面这个实例和前面的syncMoreSerial实例唯一的区别就是serialQueue task1和serialQueue task2分别在不同队列中,
这样互不影响,就不会出现崩溃死锁现象,这让我们更好理解同步+串行队列的内容。

(2)任务处理 异步+串行队列的线程选择
void asyncSerial() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 创建串行队列 */
    dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
    
    /* 2. 将任务放到队列中 */
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    
    dispatch_async(serialQueue2, ^{
        NSLog(@"serialQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue2, ^{
        NSLog(@"serialQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

执行结果:
2019-09-02 12:56:21.371933+0800 任务[5177:205067] current thread <NSThread: 0x600002c35380>{number = 1, name = main}
2019-09-02 12:56:21.372169+0800 任务[5177:205143] serialQueue task1 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372197+0800 任务[5177:205145] serialQueue2 task1 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372291+0800 任务[5177:205143] serialQueue task2 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}
2019-09-02 12:56:21.372326+0800 任务[5177:205145] serialQueue2 task2 exe thread--------<NSThread: 0x600002c5ca00>{number = 4, name = (null)}
2019-09-02 12:56:21.372513+0800 任务[5177:205143] serialQueue task3 exe thread--------<NSThread: 0x600002c5c780>{number = 3, name = (null)}

由上可以看出异步执行 + 串行队列中串行队列的thread不是主线程了,而是一条新开辟的线程,但是不管任务有多少个任务,异步执行 + 串行队列(同一条串行队列只开启一条新的线程),任务的执行顺序也是按照队列中的顺序执行的,因为同一条线程中,必须等到前一个任务执行完毕后,才能执行下一个任务。但是串行队列对应的任务是可能在多条线程中执行,多个串行队列的开辟的线程之间是可以共享使用的。

异步执行 + 串行队列 可以看到:

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

串行队列中的特殊队列,主队列MainQueue,那么异步 + 主队列对任务的处理情况会是什么样的呢?即主队列在主线程中执行异步什么情况呢?

void asyncMainQueue() {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download1------%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download2------%@",[NSThread currentThread]);
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"download3------%@",[NSThread currentThread]);
    });
}

运行结果:
2019-09-02 13:21:20.124919+0800 任务[5355:235069] download1------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125129+0800 任务[5355:235069] download2------<NSThread: 0x6000030ff780>{number = 1, name = main}
2019-09-02 13:21:20.125332+0800 任务[5355:235069] download3------<NSThread: 0x6000030ff780>{number = 1, name = main}

异步执行虽然有开启新线程的能力, 但是异步执行 + 主队列并不会开启新的线程, 任务都是在主线程中执行的。

(3)任务处理 同步+并发队列的线程选择
void syncConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 创建一条并发队列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
    /* 2. 把任务放到队列中 */
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

执行结果:
2019-09-02 16:10:13.360714+0800 任务[11194:422301] current thread <NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.360871+0800 任务[11194:422301] concurrentQueue task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361007+0800 任务[11194:422301] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361111+0800 任务[11194:422301] concurrentQueue task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361213+0800 任务[11194:422301] concurrentQueue2 task2 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}
2019-09-02 16:10:13.361308+0800 任务[11194:422301] concurrentQueue task3 exe thread--------<NSThread: 0x60000299d3c0>{number = 1, name = main}

同步执行 + 并发队列中可看到:

  • 所有任务都是在当前线程(主线程)中按照顺序依次执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
  • 所有任务都是依次打印的(同步任务 需要等待队列的任务执行结束)。
  • 任务按顺序执行的。按顺序执行的原因:虽然 并发队列 可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务 不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
(4)任务处理 异步+并发队列的线程选择
void asyncConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 创建一条并发队列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
    /* 2. 把任务放到队列中 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task1 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue2, ^{
        NSLog(@"concurrentQueue2 task2 exe thread--------%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"concurrentQueue task3 exe thread--------%@",[NSThread currentThread]);
    });
}

执行结果:
2019-09-02 16:34:33.406510+0800 任务[11384:446153] current thread <NSThread: 0x600003991400>{number = 1, name = main}
2019-09-02 16:34:33.406761+0800 任务[11384:446280] concurrentQueue2 task1 exe thread--------<NSThread: 0x6000039cbc40>{number = 4, name = (null)}
2019-09-02 16:34:33.406763+0800 任务[11384:446276] concurrentQueue task1 exe thread--------<NSThread: 0x6000039f4740>{number = 3, name = (null)}
2019-09-02 16:34:33.406794+0800 任务[11384:446277] concurrentQueue task2 exe thread--------<NSThread: 0x6000039cbcc0>{number = 5, name = (null)}
2019-09-02 16:34:33.406891+0800 任务[11384:446279] concurrentQueue task3 exe thread--------<NSThread: 0x6000039cbd40>{number = 7, name = (null)}
2019-09-02 16:34:33.406803+0800 任务[11384:446278] concurrentQueue2 task2 exe thread--------<NSThread: 0x6000039f4780>{number = 6, name = (null)}

并发队列concurrentQueue分别执行在了三条线程上。并发队列concurrentQueue2分别执行在两条线程上。并发队列创建的线程之间是可以共享使用的,看如下执行结果:

执行结果:
2019-09-02 16:38:04.019812+0800 任务[11421:451122] current thread <NSThread: 0x600000042940>{number = 1, name = main}
2019-09-02 16:38:04.020140+0800 任务[11421:451244] concurrentQueue task2 exe thread--------<NSThread: 0x600000026680>{number = 6, name = (null)}
2019-09-02 16:38:04.020163+0800 任务[11421:451242] concurrentQueue2 task2 exe thread--------<NSThread: 0x600000026600>{number = 5, name = (null)}
2019-09-02 16:38:04.020171+0800 任务[11421:451241] concurrentQueue2 task1 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}
2019-09-02 16:38:04.020201+0800 任务[11421:451243] concurrentQueue task1 exe thread--------<NSThread: 0x6000000265c0>{number = 3, name = (null)}
2019-09-02 16:38:04.020467+0800 任务[11421:451241] concurrentQueue task3 exe thread--------<NSThread: 0x60000001d940>{number = 4, name = (null)}

这里线程4被复用了。这是因为线程4任务已经执行完毕concurrentQueue2 task1的任务空闲下来了。队列会先利用空闲下来的线程而不是去创建。当然系统创建线程也是有限制的,那么系统做多会创建多少条线程呢?

void asyncMoreConcurrent() {
    NSLog(@"current thread %@",[NSThread currentThread]);
    /* 1. 创建一条并发队列 */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i =0; i<1000; i++) {
        dispatch_async(concurrentQueue, ^{
            NSLog(@"%d concurrentQueue task1 exe thread--------%@",i,[NSThread currentThread]);
            sleep(100);
        });
    }
}

执行结果:

2019-09-02 16:47:50.451507+0800 任务[11511:464499] 58 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c00>{number = 61, name = (null)}
2019-09-02 16:47:50.451939+0800 任务[11511:464501] 60 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94200>{number = 63, name = (null)}
2019-09-02 16:47:50.451583+0800 任务[11511:464500] 59 concurrentQueue task1 exe thread--------<NSThread: 0x600003c72c80>{number = 62, name = (null)}
2019-09-02 16:47:50.451940+0800 任务[11511:464502] 61 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94280>{number = 64, name = (null)}
2019-09-02 16:47:50.452695+0800 任务[11511:464503] 62 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94340>{number = 66, name = (null)}
2019-09-02 16:47:50.452649+0800 任务[11511:464504] 63 concurrentQueue task1 exe thread--------<NSThread: 0x600003c94380>{number = 65, name = (null)}

异步执行 + 并发队列 中可以看出:

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

执行方式汇总

汇总图

最后还有一个系统以及为我们创建好的并发队列,全局并发队列,首先, 它是一个并发队列, 他是系统为我们创建好的一个全局的并发队列, 所以, 有时候, 我们不需要自己创建一个并发队列, 直接用系统为我们提供的全局队列就可以了,所以全局队列和同步执行以及异步执行的组合同并发队列是一样的。

本文参考于 https://www.jianshu.com/p/8a6141c893d9, 如有侵权,请原著与我沟通,可以立即取消发布。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容