iOS开发之多线程

进程

1.进程是指在系统中正在运行的一个应用程序
2.每个进程之间是相互独立的,每个进程均运行在其专用且受保护的内存空间中

线程

1.一个进程要想执行任务,必须得有线程(每一个进程至少要有一条线程)
2.一个进程(程序)的所有任务都在线程中执行

线程的串行

1.一个线程中任务的执行是串行的
2.如果要在一个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
3.也就是说,在同一时间内,一个线程只能执行一个任务
4.因此,也可以认为线程是进程中的一条执行路径

进程和线程的比较

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

多线程

1.一个进程中可以开启多条线程,每个线程可以并发(同时)执行不同的任务
2.多线程可以提高任务的执行效率

多线程的原理

1.同一时间,CPU只能处理一条线程,只有一条线程在执行(单核)
2.多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换)
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
4.如果线程非常多,会导致CPU在很多的线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优缺点

  • 优点:
    1.能适当提高程序的执行效率
    2.能适当提高资源利用率(CPU,内存利用率)
  • 缺点:
    1.创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1kb),栈空间(子线程512kb,主线程1mb,也可以使用-setStackSize:设置,但必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间
    2.如果开启大量的线程,会降低程序的性能
    3.线程越多,CPU在调度线程上的开销就越大
    4.程序设计更加复杂,比如线程之间的通信,多线程的数据共享

多线程在iOS开发中的应用

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

  • 主线程的主要作用:
    1.显示/刷新UI界面
    2.处理UI事件(比如点击事件,滚动事件,拖拽事件等)

  • 主线程的使用注意:
    1.不要将比较耗时的操作放到主线程中
    2.耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

  • 耗时操作的执行
    将耗时操作放在子线程(后台线程,非主线程)

  • 获得主线程

    NSThread *main=[NSThread mainThread];

  • 获得当前线程

    NSThread *current=[NSThread currentThread];

  • 判断是否是主线程
    1.number==1?
    2.类方法
    BOOL isMain= [NSThread isMainThread];
    3.对象方法
    BOOL ifMain=[current isMainThread];

  • iOS中多线程的实现方案
技术方案 简介 语言 线程生命周期 使用频率
pthread 一套通用的多线程API;
适用于unix/linux/windows等系统;
跨平台/可移植;
使用难度大
C 程序员管理 几乎不用
NSThread 使用更加面向对象;
简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD 旨在替代NSThread 等线程技术;
充分利用设备的多核
C 自动管理 经常使用
NSOperation 基于GCD(底层是GCD);
比GCD 多了一些更简单实用的功能;
使用更加面向对象
OC 自动管理 经常使用

pthread的简单使用

1.#import 
2. //创建线程对象
    pthread_t thread;
    //创建线程
    /*
     第一个参数:线程对象 传递地址
     第二个参数:线程的属性 可设为null
     第三个参数:指向函数的指针
     第四个参数:函数需要接受的参数
     */
    pthread_create(&thread, NULL, test, NULL);

void *test(void * param){ 
    return NULL;
}

NSThread的基本使用

  • 方法一
    /*
     第一个参数:目标对象
     第二个参数:方法选择器 调用的方法
     第三个参数:前面调用方法需要传递的参数 可以为nil
     */
    NSThread *subthread=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"123"];
    //设置线程的名字
    subthread.name=@"subthread";
    //优先级  0.0(最低)--0.5(默认)--1.0( 最高)
    subthread.threadPriority=1;
    //需要手动启动线程
    [subthread start];

-(void)run:(NSString *)param{
    
}
  • 方法二
//分离子线程,自动启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离子线程"];
  • 方法三
//开启一条后台线程
[self performSelectorInBackground:@selector(run:) withObject:@"开启一条后台线程"];
  • 后两种方法的优缺点
    1.优点:
    简单快捷
    2.缺点:
    没有办法拿到线程对象,无法设置优先级或其他属性

  • NSThread的生命周期
    当线程中的任务执行完毕后才被释放

  • 控制线程的状态
    1.创建线程
    init
    新建, 此时在内存中存在,但是不在可调度线程池中
    2.启动线程
    -(void)start;
    进入就绪状态,进入可调度线程池,当CPU调度时,进入运行状态,当线程任务执行完毕,自动进入死亡状态
    3.阻塞/暂停线程
    +(void)sleepUntilDate:(NSDate *)date;
    +(void)sleepForTimeInterval:(NSTimeInterval)ti;
    进入阻塞状态,推出可调度线程池,当时间到时,再次进入就绪状态,进入可调度池
    4.强制停止线程
    +(void)exit;
    进入死亡状态,一旦线程死亡了,就不能再次开启任务

  • 线程安全
    多线程的安全隐患
    1.资源共享
    一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    比如多个线程访问同一个对象,同一个变量,同一个文件
    当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

  • 安全隐患解决--互斥锁
    互斥锁使用格式

@synchronized(锁对象,通常为self){
//需要锁定的代码
}

锁:必须是全局唯一的一把锁,
锁定一份代码只用一把锁,用多把锁是无效的
1.注意加锁的位置
2.注意加锁的前提条件,多线程共享同一块资源
3.注意加锁是需要付出代价的,需要耗费性能
4.加锁的结果:线程同步
线程同步:多条线程在同一条线上执行(按顺序的执行任务)

  • 互斥锁的优缺点
    优点:
    能有效防止因多线程抢夺资源造成的数据安全问题
    缺点:
    需要消耗大量的CPU资源

  • 互斥锁的使用前提:
    多条线程抢夺同一块资源

  • 原子和非原子属性
    oc在定义属性时有nonatomic和atomic两种选择
    atomic:原子属性,为setter方法加锁(默认就是atomic)
    线程安全,但需要消耗大量的资源
    nonatomic:非原子属性,不会为setter方法加锁
    非线性安全,适合内存小的移动设备

  • iOS开发的建议
    1.所有属性都声明为nonatomic
    2.尽量避免多线程抢夺同一资源
    3.尽量将加锁,资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

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

  • 线程间通信的体现
    1.一个线程传递数据给另外一个线程
    2.在一个线程中执行完特定任务后,转到另一个线程继续执行任务
    线程间通信常用方法

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

GCD

纯c语言,提供了非常多强大的函数

  • gcd的优势
    1.gcd是苹果公司为多核的并行运算提出的解决方案
    2.gcd会自动利用更多的cpu内核(比如双核,四核)
    3.gcd会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
    4.程序员只需要告诉gcd想要执行什么任务,不需要编写任何线程管理代码

  • 任务和队列
    任务:执行什么操作
    队列:用来存放任务

  • gcd的使用就两个步骤
    1.定制任务,确定想做的事情
    2.将任务添加到队列中,gcd会自动将队列中的任务取出,放到对应的线程中执行

  • 任务的取出遵循队列的FIFO原则:先进先出

  • 执行任务
    同步:只能在当前线程中执行任务,不具备开启新线程的能力,需要立刻执行,执行完毕再执行下面的
    dispatch_sync(dispatch_queue_t queue, ^(void)block);
    异步:可以在新的线程中执行任务,具备开启新线程的能力,即使没有执行完毕,后面的也可以执行
    dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);

  • 队列
    并发队列:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    并发功能只有在异步函数下才有效
    串行队列:让任务一个接着一个地执行(一个任务执行完毕后,在执行下一个任务)

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

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

  • gcd的基本使用

//1.创建队列
    /*
     第一个参数:c语言的字符串,就是一个标识符,用来区分队列,没有实际意义
     第二个参数:队列类型
     DISPATCH_QUEUE_CONCURRENT 并行队列
     DISPATCH_QUEUE_SERIAL 串行队列
     */
   dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);//方法一
    /*
     第一个参数:队列优先级
     第二个参数:暂时无用,写0即可
     */
    dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//方法二,获得全局并发队列
    
  //2.封装任务,添加任务到队列中
    /*
     第一个参数:队列
     第二个参数:要执行的任务
     */
    dispatch_async(queue, ^{
        
    });
  并发队列 串行队列 主队列
异步函数 会开启多条线程,队列中的任务是并发执行 会开启线程,开一条线程,队列中的任务是串行执行 不会开启线程,所有任务都在主线程中执行,队列中的任务是串行执行
同步函数 不会开启线程,队列中的任务是串行执行 不会开启线程,队列中的任务是串行执行 在主线程中会产生死锁,在子线程中没有影响
  • 并发队列
    两种创建方法:
    1.自己创建
dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);

2.获得全局并发队列

dispatch_queue_t queue1=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 串行队列
    两种创建方法
    1.自己创建
dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);

2.获得主队列(和主线程相关的队列)

dispatch_queue_t queue1=dispatch_get_main_queue();
  • 主队列是gcd自带的一种特殊的串行队列

  • 放在主队列中的任务,都会放到主线程中执行

  • 主队列特点
    如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲位置

  • gcd实现线程间通信
    gcd同步/异步函数嵌套使用同步/异步函数

  • gcd的常用函数

  1. 延迟执行
    /*
     第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
     第二个参数:延迟的时间 gcd时间单位:纳秒
     第三个参数:队列
     */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });
  1. 一次性代码,不能放在懒加载中,主要用在单例中
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       
   });
  1. gcd栅栏函数
    3.1 作用:控制多线程中并发任务的执行顺序,比如放置在任务1,任务2之后,任务3之前,则结果是等任务1,2都执行完毕后,再执行任务3
    3.2 注意:栅栏函数不能使用全局并发队列
    /*
     第一个参数:队列
     第二个参数:操作
     */
    dispatch_barrier_async(queue, ^{
        //操作
    });
  1. gcd快速迭代(遍历)
    for循环是同步的,gcd是开子线程和主线程一起完成遍历任务,任务的执行是并发的
    /*
     第一个参数:遍历的次数
     第二个参数:队列(只能传并发队列,串行队列无效果,主队列会死锁)
     第三个参数:index 索引
     */
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%zd",index);
    });
  1. gcd队列组
    gcd队列组的使用
    dispatch_queue_t queue2=dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group=dispatch_group_create();
    /*
     作用:
     第一个:封装任务
     第二个:把任务添加到队列中
     第三个:会监听任务的执行情况,通知group
     */
    dispatch_group_async(group, queue2, ^{
        
    });
    dispatch_group_async(group, queue2, ^{
        
    });
    dispatch_group_async(group, queue2, ^{
        
    });
    //拦截通知,当队列组中所有的任务都执行完毕的时候进入到下面的方法
    //内部本身是异步的
    dispatch_group_notify(group, queue2, ^{
        
    });

老式写法

    dispatch_queue_t queue3=dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group1=dispatch_group_create();
    //在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
    //enter和leave必须配套出现
    dispatch_group_enter(group1);
    dispatch_sync(queue3, ^{
        //离开群组
        dispatch_group_leave(group1);
    });
    
    dispatch_group_enter(group1);
    dispatch_sync(queue3, ^{
        //离开群组
        dispatch_group_leave(group1);
    });
    
    //等待 等价于dispatch_group_notify,本身是阻塞的,即我不执行,下面的也不执行
    //DISPATCH_TIME_FOREVER 表示死等,直到队列组中所有的任务都执行完毕之后才执行,
    dispatch_group_wait(group1, DISPATCH_TIME_FOREVER);

NSOperation

  • 作用:
    配合使用NSOperation和NSOperationQueue也能实现多线程编程

  • 具体步骤:
    1.先将需要执行的操作封装到一个NSOperation对象中
    2.将NSOperation对象添加到NSOperationQueue中
    3.系统会自动将NSOperationQueue中的NSOperation取出来
    4.将取出的NSOperation封装的操作放到一条新线程中执行

  • NSOperation的子类
    NSOperation是个抽象类,并不具备封装操作的能力,必须使用他的子类

  • 使用NSOperation子类的方式有3中
    1.NSInvocationOperation

//1.创建操作,封装任务
    /*
     第一个参数:目标对象
     第二个:调用的方法
     第三个:调用的方法的参数
     */
    NSInvocationOperation *op1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];

2.NSBlockOperation

//1.创建操作,封装任务
NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
        //任务
    }];
//追加任务
    //注意:如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
    //注意:不一定是子线程,有可能是主线程
    [op2 addExecutionBlock:^{
        //任务
    }];

3.自定义子类继承NSOperation,实现内部的main方法

//告知要执行的任务是什么
//有利于代码隐蔽,有益于代码复用
-(void)main{
   //任务
}

创建好操作后

//2.启动/执行操作
//注意:如果直接start,那么不会创建新线程,就和一般的[self 方法名];效果一样,无法实现多线程编程
    [op1 start];
    [op2 start];
//2.创建队列,这样才可能实现多线程编程
    /*
     主队列:[NSOperationQueue mainQueue]和gcd中的主队列一样,是串行队列,在主线程中执行
     非主队列:[[NSOperationQueue alloc]init]非常特殊,因为同时具备并发和串行的功能
     默认情况下,非主队列是一个并发队列
     */
    
    NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    //3. 添加操作到队列中,内部已经调用了start方法
    [queue addOperation:op2];
    
    //简便方法   首先创建了NSBlockOperation操作,然后把操作添加到队列里
    [queue addOperationWithBlock:^{
        
    }];

NSOperation的其他用法

NSOperationQueue *queue1=[[NSOperationQueue alloc]init];
    //设置最大并发数:同一时间最多有多少个操作/任务可以执行
    //设置为1则为串行队列,但是不等于只开一条线程
    //不能设置为0,因为会不执行任务
    //大于1就是并发队列
    //设置为-1则代表最大值,不受限制
    queue.maxConcurrentOperationCount=5;
    //暂停 可以恢复 不能暂停当前正在执行的任务,需要等当前任务执行完毕再暂停
    //队列中的任务也是有状态的  已经执行完毕/正在执行/排队等待执行
    [queue setSuspended:YES];
    //继续
    [queue setSuspended:NO];
    //取消  不可以恢复 不能暂停当前正在执行的任务,需要等当前任务执行完毕再暂停
    //该方法内部调用了所有操作的cancel方法
    //自定义NSOperation
    gh *op3=[[gh alloc]init];
//需要先在自定义的gh中的main方法中设置
    if (op3.isCancelled==YES) {
        return;
    }
    [queue cancelAllOperations];

NSOperation操作依赖和监听

   //添加操作依赖
    //注意:不能循环依赖,不会崩溃,但是谁都不会执行  可以跨队列依赖
    [op1 addDependency:op2];
    [op2 addDependency:op3];
    
    //操作监听
    op3.completionBlock = ^{
        //操作
    };

NSOperation实现线程间通信

NSOperationQueue *queue3=[[NSOperationQueue alloc]init];
 NSBlockOperation *down=[NSBlockOperation blockOperationWithBlock:^{
        
        //操作
        
        //在主线程中更新UI
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            //在主线程操作UI更新
        }];
    }];
    
    [queue3 addOperation:down];

以上差不多就是iOS中多线程的知识点了 ,如果哪里有错误,还希望告知一下

你可能感兴趣的:(iOS开发之多线程)