博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多线程基础知识
阅读量:6294 次
发布时间:2019-06-22

本文共 28237 字,大约阅读时间需要 94 分钟。

1. 进程和线程的相关概念

1.1什么是进程

进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,比如同时打开QQ、Xcode,系统就会分别启动2个进程,通过“活动监视器”可以查看Mac系统中所开启的进程.要是同时开启两个播放器,那么就是开启了两个进程。

1.2什么是线程

1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程),线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行 比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行.

1.3线程的串行

1个线程中任务的执行是串行的,如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务,比如在1个线程中下载3个文件(分别是文件A、文件B、文件C),a->b->c,c最后执行完。

1.4 进程和线程的比较

  • 线程是CPU调用(执行任务)的最小单位。
  • 进程是CPU分配资源和调度的单位。
  • 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程。
  • 同一个进程内的线程共享进程的资源。

2. 多线程的相关概念

2.1什么是多线程

1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。

多线程技术可以提高程序的执行效率,比如在一个进程中同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C) 。

2.2 多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。

思考:如果线程非常非常多,会发生什么情况? CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源每条线程被调度执行的频次会降低(线程的执行效率降低)

多核时代改变了这一情况,但是GCD等函数帮助我们不需管理核与核之间的切换。

2.3 多线程的优缺点

多线程的优点

  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU、内存利用率)

多线程的缺点

  • 开启线程需要占用一定的内存(栈)空间(默认情况下,主线程占用1M,子线程占用512KB,90毫秒的创建时间),如果开启大量的线程,会占用大量的内存空间,降低程序的性能 线程越多,CPU在调度线程上的开销就越大
  • 程序设计更加复杂:比如线程之间的通信、多线程的数据共享

2.4 多线程在iOS开发中的应用

主线程:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。

主线程的主要作用

  • 显示\刷新UI界面
  • 处理UI事件(比如点击事件、滚动事件、拖拽事件等)

主线程的使用注意:别将比较耗时的操作放到主线程中。

耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。

  • 当在主线程执行一个for循环10000次,想挪动textfiled的内容,但是无法办到,并且点击事件已经放到主线程里了,只等for循环结束就执行这个操作。

3. NSThread的使用

3.1 创建NSThread

以下几个开辟线程的方法,一个NSThread对象就代表一条线程。

创建、启动线程

/**     target: 目标对象 self     selector: run 方法选择器 - 调用的方法     object : 前面调用方法需要传递的参数     */    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"111"];    thread.name = @"name1";    thread.threadPriority = 0.9;    [thread start];     //从当前线程中分离出子线程 - 没有返回值    [NSThread detachNewThreadSelector:@selector(run2:) toTarget:self withObject:@"222"];        [self performSelector:@selector(run2:) withObject:@"开启后台线程"];复制代码

NSThread的相关用法

  • + (NSThread *)mainThread; - 获得主线程
  • -(BOOL)isMainThread; - 是否为主线程
  • + (BOOL)isMainThread; - 是否为主线程
  • NSThread *current = [NSThread currentThread]; - 获得当前线程
NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"111"];    threadA.name = @"nameA";    threadA.threadPriority = 0.9;    [threadA start];     NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"111"];    threadB.name = @"nameB";    threadB.threadPriority = 0.5;    [threadB start];        NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"111"];    threadC.name = @"nameC";    threadC.threadPriority = 0.1;    [threadC start];复制代码
- (void)run:(NSString *)str{        for (NSInteger i = 0; i<100; i++) {        NSLog(@"%zd --- %@",i,[NSThread currentThread].name);    }   }OC知识点合集[533:289192] 0 --- nameAOC知识点合集[533:289192] 1 --- nameA...OC知识点合集[533:289192] 20 --- nameAOC知识点合集[533:289193] 0 --- nameBOC知识点合集[533:289194] 0 --- nameC...OC知识点合集[533:289193] 99 --- nameBOC知识点合集[533:289194] 1 --- nameC复制代码

线程的调度优先级:调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高

  • + (double)threadPriority;
  • + (BOOL)setThreadPriority:(double)p;
  • 在多条线程中比较有用

设置线程的名字 - thread.name = @"name1";

performSelectorInBackgrounddetachNewThreadSelector的创建方法优缺点 优点:简单快捷 缺点:无法对线程进行更详细的设置

3.2 NSThread线程间通信

  • performSelectorOnMainThread
  • performSelector:target onThread:
- (void)threadCommunication{    UIImageView *imageView = [[UIImageView alloc] init];    imageView.frame = CGRectMake(100, 100, 100, 100);    [self.view addSubview:imageView];    self.imageV = imageView;        NSURL *url = [NSURL URLWithString:@"http://img3.duitang.com/uploads/blog/201402/05/20140205123502_WyAa4.thumb.700_0.jpeg"];    //也可以用NSDate获取当前时间 CFTimeInterval是一个绝对值,从2001年开始算起    CFTimeInterval start = CFAbsoluteTimeGetCurrent();        NSData *data = [NSData dataWithContentsOfURL:url];    UIImage *image = [UIImage imageWithData:data];    CFTimeInterval end = CFAbsoluteTimeGetCurrent();    NSLog(@"end - start = %f",end - start);    /**     回到主线程刷新UI     MainThread:选择器 - 回到主线程调用哪个方法     Object: 前面方法需要传递的参数     UntilDone: 是否等待,有可能在performSelectorOnMainThread这个消息下还有代码,这个等待为true则等执行完再执行下面代码     *///    两个都是给imageView赋值 ,但是下面的少些一步 ,因为UIImageView中的image是用@property修饰,有set方法//    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];//    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];    [self.imageV performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];    NSLog(@"%@",[NSThread currentThread]);}- (void)showImage:(UIImage *)image{    self.imageV.image = image;}复制代码

一些用法

//创建线程 NSThread通过alloc init开辟空间的话,要去自定义的头文件找指定的任务    DXThread *thread = [[DXThread alloc] init];        //启动线程    [thread start];复制代码

4.线程的安全

4.1多线程的安全隐患

资源共享 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。比如多个线程访问同一个对象、同一个变量、同一个文件,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。最经典的例子就是银行存取钱,要是同时操作的话,原有1000元,一边从支付宝存1000元,一遍取500元,最后返回500元了。

4.2如何解决

互斥锁使用格式 @synchronized(锁对象) { // 需要锁定的代码 } 注意:锁定1份代码只用1把锁,用多把锁是无效的。 互斥锁的优缺点 优点:能有效防止因多线程抢夺资源造成的数据安全问题 缺点:需要消耗大量的CPU资源

互斥锁的使用前提:多条线程抢夺同一块资源 相关专业术语:线程同步,多条线程按顺序地执行任务,互斥锁,就是使用了线程同步技术。

4.3死锁

4.3.1死锁简单介绍

死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

发生死锁的情况一般是两个对象的锁相互等待造成的。   那么为什么会产生死锁呢?有3个原因:第一,因为系统资源不足;第二,进程运行推进的顺序不合适(程序员的bug啦);第三,资源分配不当;第四, 占用资源的程序崩溃 。

产生死锁的条件有四个:   (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。  

死锁通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在相互等待,造成了无法执行的情况。  

4.3.2死锁的解决方案

避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。 

预防 理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源,在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配 。因此,对资源的分配要给予合理的规划。

一、有序资源分配法 这种算法资源按某种规则系统中的所有资源统一编号(例如打印机为1、磁带机为2、磁盘为3、等等),申请时必须以上升的次序。系统要求申请进程: 1、对它所必须使用的而且属于同一类的所有资源,必须一次申请完; 2、在申请不同类资源时,必须按各类设备的编号依次申请。例如:进程PA,使用资源的顺序是R1,R2; 进程PB,使用资源的顺序是R1,R2;若采用动态分配有可能形成环路条件,造成死锁。 采用有序资源分配法:R1的编号为1,R2的编号为2; PA:申请次序应是:R1,R2 PB:申请次序应是:R1,R2 这样就破坏了环路条件,避免了死锁的发生

二、银行算法 避免死锁算法中最有代表性的算法是Dijkstra E.W 于1968年提出的银行家算法: 该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的请求,就满足申请者的请求。 这样申请者就可很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生。

避免死锁 最具有代表性的避免死锁的算法,是Dijkastra的银行家算法,这是由于该算法能用于银行系统现金贷款的发放而得名的,为实现银行家算法,系统中必须设置若干数据结构. 1)可利用资源向量Available 是个含有m个元素的数组,其中的每一个元素代表一类可利用的资源数目。如果Available[j]=K,则表示系统中现有Rj类资源K个。 2)最大需求矩阵Max 这是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数目为K。 3)分配矩阵Allocation 这也是一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程i当前已分得Rj类资源的 数目为K。 4)需求矩阵Need。 这也是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要Rj类资源K个,方能完成其任务。  Need[i,j]=Max[i,j]-Allocation[i,j]

排除方法 1、撤消陷于死锁的全部进程; 2、逐个撤消陷于死锁的进程,直到死锁不存在; 3、从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失; 4、从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态。  http://zhidao.baidu.com/link?url=ITQn-LvJKNXJg6tbMdgJlM1oFegZptPwakvMrNkYPJWHK6tl9cpvCIqIrHCOtmyLcPaggkvvK_lvPuUdaw4zrflgPlPioVwcY9tAdDjFc5O

5.线程间的通信

5.1 简单说明

线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信.

线程间通信的体现:

  • 1个线程传递数据给另1个线程
  • 在1个线程中执行完特定任务后,转到另1个线程继续执行任务

线程间通信常用方法

//aSelector : 回到主线程调用这个方法- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;//thread: 传入一个线程去执行任务aSelector- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;复制代码

![示例](./屏幕快照 2018-05-04 下午1.30.21.png)

[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];复制代码

![NSObject写的分类](./屏幕快照 2018-05-04 下午2.56.37.png)

意义就是凡是对象都可以调用这些方法

6. GCD

6.1简单介绍

6.1.1 什么是GCD?

全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”,纯C语言,提供了非常多强大的函数。在iOS6之后纳入ARC的内存管理范畴,不需要对create和retain函数做realease操作,就像对待oc对象一样对待就好。

6.1.2.GCD的优势

  • GCD是苹果公司为多核的并行运算提出的解决方案。

  • GCD会自动利用更多的CPU内核(比如双核、四核)。

  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。

  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。

6.1.3 核心概念-任务和队列

GCD中有2个核心概念:

  • 任务:执行什么操作。
  • 队列:用来存放任务,调度任务,安排任务在哪个线程中执行。

GCD的使用就2个步骤:

  • 定制任务
    • 确定想做的事情
  • 将任务添加到队列中
    • GCD会自动将队列中的任务取出,放到对应的线程中执行

GCD只支持FIFO(First In First Out )的队列原则(两个口);而像栈这种数据结构就是先进后出,后进先出(一个口)。

6.1.3.1 执行任务

GCD中有2个用来执行任务的函数:

  1. 用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  2. 用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

说明:这两个函数的作用就是把右边的参数(封装好任务)提交给左边的参数(添加任务到队列中,队列调度任务)进行执行。 参数说明:

  • queue:队列
  • block:封装任务

同步和异步的区别:

  • 同步:只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步:可以在新的线程中执行任务,具备开启新线程的能力。

还有一种方式能开启异步线程,跟dispatch_async区别就是封装的方式不一样,其他都是一样的

{    /*     - 队列     - 给后面函数传递的参数     - 要调用的函数名称     */    dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, task);    }void task(void *parm){    NSLog(@"%s",__func__);}复制代码
6.1.3.2 队列的类型

GCD的队列可以分为2大类型

  • 并发队列(Concurrent Dispatch Queue)

    • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效,并且只有在异步函数下才有效。
    • 那么在异步函数和并发队列组合下会开启子线程
  • 串行队列(Serial Dispatch Queue)

    • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

几个容易混淆的术语

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

  • 同步和异步决定了要不要开启新的线程,它指的是GCD的两个函数

    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 并发和串行并不是指任务的执行方式 - 而是指的队列

    • 并发:多个任务并发(同时)执行
    • 串行:一个任务执行完毕后,再执行下一个任务

6.1.4 串行队列

GCD中获得串行有2种途径

  • 使用dispatch_queue_create函数创建串行队列

dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

示例:

dispatch_queue_t queue = dispatch_queue_create("wendingding", DISPATCH_QUEUE_SERIAL); dispatch_release(queue); // 非ARC需要释放手动创建的队列复制代码
  • 使用主队列(跟主线程相关联的队列)

主队列是GCD自带的一种特殊的串行队列,跟主线程相关联的队列,放在主队列中的任务,都会放到主线程中执行。

使用dispatch_get_main_queue()获得主队列

示例:

dispatch_queue_t queue = dispatch_get_main_queue();

6.1.5 并发队列

GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建。

使用dispatch_get_global_queue函数获得全局的并发队列

dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);

示例:

//获得全局并发队列    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        /**     - 第一个参数 long identifier: 优先级        - DISPATCH_QUEUE_PRIORITY_DEFAULT:默认        - DISPATCH_QUEUE_PRIORITY_BACKGROUND - 最低     - 第二个参数 unsigned long flags: 给未来使用,一般传个0     */复制代码

说明:全局并发队列的优先级

define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高	 define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)	 define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低	 define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台复制代码
- (void)viewDidLoad {     [super viewDidLoad];     //1.获得全局的并发队列    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);     //2.添加任务到队列中,就可以执行任务     //异步函数:具备开启新线程的能力     dispatch_async(queue, ^{         NSLog(@"下载图片1----%@",[NSThread currentThread]);     });    dispatch_async(queue, ^{         NSLog(@"下载图片2----%@",[NSThread currentThread]);     });     dispatch_async(queue, ^{         NSLog(@"下载图片2----%@",[NSThread currentThread]);     }); //打印主线程     NSLog(@"主线程----%@",[NSThread mainThread]);//总结:同时开启三个子线程 }复制代码

小结: 说明:异步函数具备开启线程的能力,而开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。

补充:

  • 凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。
  • GCD的数据类型在ARC的环境下不需要再做release。
  • CF(core Foundation)的数据类型在ARC环境下还是需要做release。
  • 两种获得并发队列的方法区别:一种是创建并发队列,一种是拿系统的全局并发队列来用。
  • GCD的异步并发队列并不是我们有多少个任务就有开启多少个线程,它不受我们控制,有可能10个任务开10个子线程,也有可能10个任务开3个子线程,具体是由系统决定。

6.2 几种搭配用法

并发队列 手动创建的串行队列 主队列
同步(sync) 没有开启新线程;串行执行任务 没有开启新线程;同步执行任务 死锁
异步(sync) 开启新线程;并发执行任务 开启一条新线程;同步执行任务 在主线程中同步执行任务
  • 不再主线程中的异步函数才会开启子线程。
  • 开启几条子线程就要看队列的类型。
  • 串行队列1条线程,并发队列多条线程。

  • 异步函数+并发队列 - 会开启子线程,并并发执行任务
- (void)asycnConcurrent{    //1. 创建队列    /**     - 第一个参数 const char * _Nullable label: C语言的字符串,标识这个线程(可以有多个队列,需要区分)     - 第一个参数 dispatch_queue_attr_t  _Nullable attr: 队列的类型        - DISPATCH_QUEUE_CONCURRENT - 并发队列        - DISPATCH_QUEUE_SERIAL - 串行队列     */    dispatch_queue_t queue = dispatch_queue_create("com.ipa361.text1", DISPATCH_QUEUE_CONCURRENT);    NSLog(@"---start-----");    //2. 封装任务    /**     - 第一个参数 队列     - 第一个参数 要执行的任务(本质就是一个匿名函数)     - DISPATCH_QUEUE_CONCURRENT - 并发队列     - DISPATCH_QUEUE_SERIAL - 串行队列     */    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 1 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 2 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 3 ---%@",[NSThread currentThread]);    });     NSLog(@"---end-----");    /**     ---start-----     ---end-----     {number = 2,name = (null)};     {number = 4,name = (null)};     {number = 3,name = (null)};     */}复制代码
  • 异步函数+串行队列 - 会开启子线程,开一条线程,队列中的任务是串行执行的
    • 一般来说串行队列不需要赋值任何属性,所以可以传空值(NULL)
- (void)asycnSerial{    dispatch_queue_t queue = dispatch_queue_create("com.ipa361.text1", DISPATCH_QUEUE_SERIAL);	     dispatch_async(queue, ^{        NSLog(@"asycnSerial 1 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnSerial 2 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnSerial 3 ---%@",[NSThread currentThread]);    });         /**     {number = 2,name = (null)};     {number = 2,name = (null)};     {number = 2,name = (null)};     */}复制代码
  • 同步函数+并发队列 - 不会开线程,任务是串行执行的
- (void)sycnConcurrent{    dispatch_queue_t queue = dispatch_queue_create("com.ipa361.text1", DISPATCH_QUEUE_CONCURRENT);    NSLog(@"---start-----");    dispatch_sync(queue, ^{        NSLog(@"sycnConcurrent 1 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"sycnConcurrent 2 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"sycnConcurrent 3 ---%@",[NSThread currentThread]);    });    NSLog(@"---end-----");    /**     ---start-----     {number = 1,name = main};     {number = 1,name = main};     {number = 1,name = main};     ---end-----     */}复制代码
  • 同步函数+串发队列 - 不会开线程,任务是串行执行的
- (void)sycnSerial{    dispatch_queue_t queue = dispatch_queue_create("com.ipa361.text1", DISPATCH_QUEUE_SERIAL);    dispatch_sync(queue, ^{        NSLog(@"sycnSerial 1 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"sycnSerial 2 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"sycnSerial 3 ---%@",[NSThread currentThread]);    });    /**     {number = 1,name = main};     {number = 1,name = main};     {number = 1,name = main};     */}复制代码
  • 异步函数+主队列 - 不会开启线程,串行执行
- (void)asycncain{    //1. 创建队列    dispatch_queue_t queue = dispatch_get_main_queue();    //2. 封装任务交给队列 异步函数,我这堵着呢但是其他地方可以执行    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 1 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 2 ---%@",[NSThread currentThread]);    });    dispatch_async(queue, ^{        NSLog(@"asycnConcurrent 3 ---%@",[NSThread currentThread]);    });    /**     {number = 1,name = main};     {number = 1,name = main};     {number = 1,name = main};     */}复制代码
  • 同步函数+主队列 - 导致死锁

    • 主队列把封装的任务交给主线程,而同步函数又是调用的主线程,这样互相等待了。
      • 主队列的特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务直到主线程空闲为止。
      • 同步函数的特点就是马上执行,并且要等我的事情执行完毕之后才能执行其他的任务。
      • 当queue把block从主队列拿出来想放到主线程中,发现主线程正在等待dispatch_sync执行完毕,这就导致互相等待导致死锁。
  • 要是在子线程中执行syncMain消息的话,它不会造成死锁。因为是在子线程,主线程没有任务是空闲状态,所以才不会造成死锁。

- (void)syncMain{    //1. 创建队列    dispatch_queue_t queue = dispatch_get_main_queue();    //2. 封装任务交给队列    dispatch_sync(queue, ^{        NSLog(@"asycnConcurrent 1 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"asycnConcurrent 2 ---%@",[NSThread currentThread]);    });    dispatch_sync(queue, ^{        NSLog(@"asycnConcurrent 3 ---%@",[NSThread currentThread]);    });	NSLog(@"_____end");}复制代码

6.3 GCD的线程通信

GCD实现线程之间通信手段靠嵌套。

  • 从子线程回到主线程
//1. 创建子线程    // 第一个参数:队列    // 第二个参数:    //因为是一张图下载所以,并发队列或者串行队列都可以,所以这里使用全局并发队列    //dispatch_get_global_queue 第一个参数是优先级 第二个参数留给未来使用 总是传0    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        NSURL *url = [NSURL URLWithString:@"http://img3.duitang.com/uploads/blog/201402/05/20140205123502_WyAa4.thumb.700_0.jpeg"];        NSData *data = [NSData dataWithContentsOfURL:url];        UIImage *image = [UIImage imageWithData:data];        //更新UI,在子线程中 async和sync都是可以的,只有在主线程中同步和主队列放在一起用回造成死锁        dispatch_async(dispatch_get_main_queue(), ^{            self.imageV.image = image;        });    });}复制代码

6.4 GCD的常用函数

6.4.1 延迟执行

  • 调用NSObject方法的performSelector:@selector(run) withObject:nil afterDelay:2.0
  • 调用NSTimer的scheduledTimerWithTimeInterval
  • GCD的dispatch_after
//延迟执行- (void)delay{    //1. 第一种    [self performSelector:@selector(task) withObject:nil afterDelay:2.0];    //2. 第二种    //Interval: 时间; repeats:是否重复调用    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];    //GCD    /*     DISPATCH_TIME_NOW - 从现在开始计算时间     delayInSeconds - 要延迟的时间 (GCD的事件单位为纳秒,所以要*NSEC_PER_SEC(10的9次方))     dispatch_get_main_queue - 队列     GCD的延迟执行的优点是能控制延迟执行之后block能在哪个线程中执行;dispatch_get_main_queue是在主线程中,dispatch_get_global_queue是在子线程中     */    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        //代码块        NSLog(@"%@",[NSThread currentThread]);    });}复制代码

说明

  • NSObject方法在哪个线程调用,那么task就在哪个线程执行(当前线程),通常是主线程。
- (void)viewDidLoad{    [super viewDidLoad];    NSLog(@"打印线程----%@",[NSThread currentThread]);    //延迟执行    //第一种方法:延迟3秒钟调用run函数    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];    }-(void)run{    NSLog(@"延迟执行----%@",[NSThread currentThread]);}-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    //在异步函数中执行    dispatch_queue_t queue = dispatch_queue_create("wendingding", DISPATCH_QUEUE_CONCURRENT);        dispatch_async(queue, ^{        [self performSelector:@selector(test) withObject:nil afterDelay:1.0];    });    NSLog(@"异步函数");}-(void)test{    NSLog(@"异步函数中延迟执行----%@",[NSThread currentThread]);}复制代码

6.4.2 一次性代码

需求:点击控制器只有第一次点击的时候才打印,或者是单例。

#import "YYViewController.h"@interface YYViewController ()@property(nonatomic,assign) BOOL log;@end@implementation YYViewController-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    if (_log==NO) {        NSLog(@"该行代码只执行一次");        _log=YES;    }}@end//缺点:这是一个对象方法,如果又创建一个新的控制器,那么打印代码又会执行,因为每个新创建的控制器都有自己的布尔类型,且新创建的默认为NO,因此不能保证改行代码在整个程序中只打印一次。复制代码
  • 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次.

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

// 只执行1次的代码(这里面默认是线程安全的)复制代码

});

整个程序运行过程中,只会执行一次。

#import "YYViewController.h"@interface YYViewController ()@property(nonatomic,assign) BOOL log;@end@implementation YYViewController-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        NSLog(@"该行代码只执行一次");    });}@end复制代码

注意:懒加载中不要使用dispatch_once函数

6.4.3 栅栏函数

需求就是有的任务依赖于其他线程先执行,之后再执行。

  • 栅栏函数 - 想barrier1、2任务执行完之后再执行barrier3
  • 栅栏函数不能使用全局并发队列,要不然有问题,只能使用自己创建的并发队列
  • 栅栏函数之前的异步函数无法控制
- (void)barrier{//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);    dispatch_queue_t queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);    dispatch_async(queue, ^{        for (int i = 0; i<100; i++) {            NSLog(@"barrier 1 ---%@",[NSThread currentThread]);        }    });    dispatch_async(queue, ^{        for (int i = 0; i<100; i++) {            NSLog(@"barrier 2 ---%@",[NSThread currentThread]);        }    });        dispatch_barrier_async(queue, ^{        NSLog(@"barrier 3 ---%@",[NSThread currentThread]);    });        dispatch_async(queue, ^{        for (int i = 0; i<100; i++) {            NSLog(@"barrier 3 ---%@",[NSThread currentThread]);        }    });}复制代码

6.4.4 快速迭代

快速迭代之所以会比for循环快,是因为快速迭代内部会开启子线程(主线程也会参与执行任务),而for是在主线程中执行的。

/*     第一个参数:循环次数     第二个参数:队列(并发队列,传入主队列的话会死锁)     第三个参数:索引     */    dispatch_apply(10,dispatch_get_global_queue(0,0),^(size_t index){        NSLog(@"%zd --- %@",index,[NSThread currentThread]);    });复制代码

做一个文件剪切案例,在一个form文件夹中有三张图片,需求就是把这个三张图片截切到to文件夹中。

//剪切图片- (void)moveFile{    // 拿到文件路径    NSString *from = @"User/Deskop/from";    // 获得目标文件路径    NSString *to = @"User/Deskop/to";    // 获得目标路径下所有文件    NSArray *array = [[NSFileManager defaultManager] subpathsAtPath:from];    //遍历所有文件,之后执行剪切操作    NSInteger count = array.count;    for (NSInteger i = 0; count; i++) {        //4.1 拼接文件全路径 stringByAppendingPathComponent在拼接的时候回自动添加斜杠        NSString *fullPath = [from stringByAppendingPathComponent:array[i]];        NSString *toFullPath = [to stringByAppendingPathComponent:array[i]];        //4.2 执行剪切操作        /*         第一个参数:要剪切的文件在哪         第二个参数:文件应该被放在哪里         第三个参数:错误, 传地址         */        [[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];            }}- (void)moveFileWithGCD{    // 拿到文件路径    NSString *from = @"User/Deskop/from";    // 获得目标文件路径    NSString *to = @"User/Deskop/to";    // 获得目标路径下所有文件    NSArray *array = [[NSFileManager defaultManager] subpathsAtPath:from];    //遍历所有文件,之后执行剪切操作    NSInteger count = array.count;    dispatch_apply(count,dispatch_get_global_queue(0,0),^(size_t i){        //4.1 拼接文件全路径 stringByAppendingPathComponent在拼接的时候回自动添加斜杠        NSString *fullPath = [from stringByAppendingPathComponent:array[i]];        NSString *toFullPath = [to stringByAppendingPathComponent:array[i]];        [[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];            });}复制代码

6.4.5 队列组

用途:监听并发队列中的任务执行情况,可以在任务执行完成之后做逻辑处理。

  • 第一种
//1.创建一个队列组    dispatch_group_t group = dispatch_group_create();    // global_quque 全局并发队列    /*    - 封装任务    - 把任务添加到队列中    - 监听任务的完成情况,并且通知group	*/    dispatch_group_async(group, global_quque, ^{    };    //拦截通知,当队列组中所有任务都执行完毕的时候回执行下面任务	dispatch_group_notify(group, dispatch_get_main_queue(), ^{    // 等前面的异步操作都执行完毕后,回到主线程...	});复制代码
  • 第二种
    • dispatch_group_enterdispatch_group_leave配对使用
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);    dispatch_group_t group = dispatch_group_create();    //在该方法后面的异步任务会被纳入到队列组中监听范围    dispatch_group_enter(group);    dispatch_async(queue, ^{        //告诉队列住,任务执行完毕离开队列组        dispatch_group_leave(group);    });    dispatch_group_enter(group);    dispatch_async(queue, ^{        //告诉队列住,任务执行完毕离开队列组        dispatch_group_leave(group);    });    dispatch_group_notify(group, dispatch_get_main_queue(), ^{    // 等前面的异步操作都执行完毕后,回到主线程...	});	//除了dispatch_group_notify这个函数,还有一个函数能达到同样效果    //DISPATCH_TIME_FOREVER 表示死等并且这个函数是阻塞    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);	NSLog(@"end----");复制代码

问题:dispatch_group_notify是阻塞的么?那么dispatch_group_wait呢? dispatch_group_notify内部是异步线程,所以不是阻塞的。像end字符串就是第一行输出。 dispatch_group_wait内部是要等其他异步线程执行完毕才执行,所以是阻塞的。end字符串是最后一行输出。

需求:从网络上下载两张图片,把两张图片合并成一张最终显示在view上。 第一种方法

#import "YYViewController.h"//宏定义全局并发队列#define global_quque    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)//宏定义主队列#define main_queue       dispatch_get_main_queue()@interface YYViewController ()@property (weak, nonatomic) IBOutlet UIImageView *imageView1;@property (weak, nonatomic) IBOutlet UIImageView *imageView2;@property (weak, nonatomic) IBOutlet UIImageView *imageView3;@end@implementation YYViewController-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    dispatch_async(global_quque, ^{        //下载图片1       UIImage *image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];        NSLog(@"图片1下载完成---%@",[NSThread currentThread]);            //下载图片2       UIImage *image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];        NSLog(@"图片2下载完成---%@",[NSThread currentThread]);                //回到主线程显示图片        dispatch_async(main_queue, ^{             NSLog(@"显示图片---%@",[NSThread currentThread]);            self.imageView1.image=image1;            self.imageView2.image=image2;            //合并两张图片			// 开启图片上下文,告诉宽度和高度          			UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);			//画图            [image1 drawInRect:CGRectMake(0, 0, 100, 100)];            [image2 drawInRect:CGRectMake(100, 0, 100, 100)];//根据图片上下文得到一张图片            self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();            //关闭上下文            UIGraphicsEndImageContext();               NSLog(@"图片合并完成---%@",[NSThread currentThread]);        });        //    });}//封装一个方法,传入一个url参数,返回一张网络上下载的图片-(UIImage *)imageWithUrl:(NSString *)urlStr{    NSURL *url=[NSURL URLWithString:urlStr];    NSData *data=[NSData dataWithContentsOfURL:url];    UIImage *image=[UIImage imageWithData:data];    return image;}@end复制代码

问题:这种方式的效率不高,需要等到图片1.图片2都下载完成后才行。

提示:使用队列组可以让图片1和图片2的下载任务同时进行,且当两个下载任务都完成的时候回到主线程进行显示。

使用队列组

  • 创建一个组
  • 开启一个任务下载图片1
  • 开启一个任务下载图片2
  • 同时执行下载图片1\下载图片2操作
  • 等group中的所有任务都执行完毕, 再回到主线程执行其他操作
#import "YYViewController.h"//宏定义全局并发队列#define global_quque    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)//宏定义主队列#define main_queue       dispatch_get_main_queue()-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{       //1.创建一个队列组    dispatch_group_t group = dispatch_group_create();    //2.开启一个任务下载图片1    __block UIImage *image1=nil;    dispatch_group_async(group, global_quque, ^{        image1= [self imageWithUrl:@"http://d.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=2b9a12172df5e0fefa1581533d095fcd/cefc1e178a82b9019115de3d738da9773912ef00.jpg"];        NSLog(@"图片1下载完成---%@",[NSThread currentThread]);    });        //3.开启一个任务下载图片2    __block UIImage *image2=nil;    dispatch_group_async(group, global_quque, ^{        image2= [self imageWithUrl:@"http://h.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=f47fd63ca41ea8d39e2f7c56f6635b2b/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];        NSLog(@"图片2下载完成---%@",[NSThread currentThread]);    });        //同时执行下载图片1\\下载图片2操作       //4.等group中的所有任务都执行完毕, 再回到主线程执行其他操作    dispatch_group_notify(group,main_queue, ^{        NSLog(@"显示图片---%@",[NSThread currentThread]);        self.imageView1.image=image1;        self.imageView2.image=image2;                //合并两张图片        //注意最后一个参数是浮点数(0.0),不要写成0。        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];        [image2 drawInRect:CGRectMake(100, 0, 100, 100)];        self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();        //关闭上下文        UIGraphicsEndImageContext();                NSLog(@"图片合并完成---%@",[NSThread currentThread]);    });    }-(void)download2image{       dispatch_async(global_quque, ^{        //下载图片1       UIImage *image1= [self imageWithUrl:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];        NSLog(@"图片1下载完成---%@",[NSThread currentThread]);            //下载图片2       UIImage *image2= [self imageWithUrl:@"http://news.baidu.com/z/resource/r/image/2014-06-22/2a1009253cf9fc7c97893a4f0fe3a7b1.jpg"];        NSLog(@"图片2下载完成---%@",[NSThread currentThread]);                //回到主线程显示图片        dispatch_async(main_queue, ^{             NSLog(@"显示图片---%@",[NSThread currentThread]);            self.imageView1.image=image1;            self.imageView2.image=image2;            //合并两张图片            UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);            [image1 drawInRect:CGRectMake(0, 0, 100, 100)];            [image2 drawInRect:CGRectMake(0, 0, 100, 100)];            self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext();            //关闭上下文            UIGraphicsEndImageContext();               NSLog(@"图片合并完成---%@",[NSThread currentThread]);        });        //    });}//封装一个方法,传入一个url参数,返回一张网络上下载的图片-(UIImage *)imageWithUrl:(NSString *)urlStr{    NSURL *url=[NSURL URLWithString:urlStr];    NSData *data=[NSData dataWithContentsOfURL:url];    UIImage *image=[UIImage imageWithData:data];    return image;}@end复制代码
  • 补充说明

有这么1种需求:

首先:分别异步执行2个耗时的操作

其次:等2个异步操作都执行完毕后,再回到主线程执行操作

如果想要快速高效地实现上述需求,可以考虑用队列组

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 执行1个耗时的异步操作复制代码

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 执行1个耗时的异步操作复制代码

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

// 等前面的异步操作都执行完毕后,回到主线程...复制代码

});

转载于:https://juejin.im/post/5b45748ce51d45198651228c

你可能感兴趣的文章
PHP 7.1是否支持操作符重载?
查看>>
Vue.js 中v-for和v-if一起使用,来判断select中的option为选中项
查看>>
Java中AES加密解密以及签名校验
查看>>
定义内部类 继承 AsyncTask 来实现异步网络请求
查看>>
VC中怎么读取.txt文件
查看>>
如何清理mac系统垃圾
查看>>
企业中最佳虚拟机软件应用程序—Parallels Deskto
查看>>
Nginx配置文件详细说明
查看>>
怎么用Navicat Premium图标编辑器创建表
查看>>
Spring配置文件(2)配置方式
查看>>
MariaDB/Mysql 批量插入 批量更新
查看>>
ItelliJ IDEA开发工具使用—创建一个web项目
查看>>
solr-4.10.4部署到tomcat6
查看>>
切片键(Shard Keys)
查看>>
淘宝API-类目
查看>>
virtualbox 笔记
查看>>
Git 常用命令
查看>>
驰骋工作流引擎三种项目集成开发模式
查看>>
SUSE11修改主机名方法
查看>>
jdk6.0 + Tomcat6.0的简单jsp,Servlet,javabean的调试
查看>>