OC多线程

181 阅读7分钟

线程和进程


  • 进程
    • 在系统中正在运行的一个应用程序
    • 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
    • CPU分配资源和调度的单位
  • 线程
    • 一个进程要想执行任务,必须得有线程,每个进程至少要与欧一条线程
    • 一个进程的所有任务都在线程中执行CPU调用(执行任务)的最小单元
    • 同一个进程内的线程共享进程的资源
  • 线程的串行
    • 一个线程中任务的执行时串行的,如果要在一个线程中执行多个任务,那么只能一个个地按照顺序执行这些任务同一时间只能执行一个任务

多线程

  • 一个进程中可以开启多条线程,每条线程可以并行执行不同的任务
  • 同一时间,单核CPU只能处理一条数据,只有一条线程在工作
  • 多线程并发执行,其实是CPU快速地在多线程之间调度切换
  • 如果线程非常多
    • CPU会在N多线程之间调度,CPU会累死,消耗大量CPU资源
    • 每条线程被调度执行的频次会降低
  • 多线程优点
    • 能适当提高程序的执行效率
    • 能适当提高资源利用率
  • 多线程的缺点
    • 创建线程是有开销的,空间和时间上都有
    • 如果开启大量的线程,会降低程序的性能
    • 线程越多,CPU在调度线程上的开销就越大
    • 程序设计更加复杂,比如线程之间的通信,多线程的数据共享

多线程在IOS开发中的应用

  • 一个IOS程序运行后,默认会开启一条线程,成为主线程/UI线程
  • 主线程的主要作用
    • 显示/刷新UI界面
    • 处理UI事件(点击,滚动,拖拽)
  • 主线程的使用注意
    • 不要讲比较耗时的操作放到主线程
    • 耗时操作会卡住主线程,严重影响性能
    //获取主线程
    NSThread *main = [NSThread mainThread];
    //获取当前线程
    NSThread *current = [NSThread currentThread];
    
  • 判断线程是否是主线程
    • number==1
    • 类方法:BOOL isMainThread = [NSThread isMainThread];
    • 对象方法:BOOL isMainThread = [currendThread isMainThread];

ios中多线程的实现方案

  • pthread
    • c语言开发,生命周期由程序员管理,几乎不用
  • NSThread
    • 面向对象,生命周期由程序员管理,偶尔使用
  • GCD
    • C语言,生命周期自动管理,常用
  • NSOperation
    • 基于GCD,OC语言,生命周期由程序员管理

线程安全

  • 资源共享
    • 多个线程可能会访问同一块资源
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全的问题
  • 使用互斥锁解决线程安全@synchronized(){}
    • 锁必须是全局唯一的
    • 互斥锁的位置不能随便加
    • 加锁的前置条件:多线程共享同一个资源
    • 注意加锁是要付出代价的:需要额外的性能
    • 加锁的结果:线程同步(默认是异步的),多条线程在同一条线上执行(按顺序地执行任务)

原子和非原子

  • nonatomic
    • 非原子属性,不会为setter加锁,非线程安全,适合内存小的移动设备
  • atomic
    • 原子属性,为setter方法加锁,默认就是,线程安全的,需要消耗大量资源

线程间通信

//回到主线程执行
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
{

    
}
//指定线程
- (void)performSelector:(**SEL**)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(**BOOL**)wait
{

}
//继承自NSObject的对象内部自带了上述两种方法

GCD

  • 任务和队列

    • 任务:执行什么操作
    • 队列:用来存放任务,先进先出,GCD会自动将队列中的任务取出,放到对应的线程中
    //同步执行,只能在当前线程中执行任务,不具备开启新线程的能力
    dispatch_sync(<队列>, ^{
        //任务
        })
    //异步执行,可以在新的线程中执行任务,具备开启新线程的能力
    dispatch_async(队列>, ^{
        //任务
        })
    
  • 同步异步函数

    • 同步:只能在当前线程中执行任务,当前任务不执行完不会继续执行,不具备开启新线程的能力
    • 异步:可以在新的线程中执行任务,具备开启新线程的能力
    • 同步和异步主要影响:能不能开启新线程
  • 并发串行队列

    • 并发队列:可以让多个任务同步执行(自动开启多喝线程同时执行任务),只有在异步函数下才有效
    • 串行队列:让任务一个接一个的执行
    • 并发和串行主要影响:任务的执行方法
  • 异步函数+并发队列

    • 会开启多条线程,队列中的任务是并发执行的,并不是有多少任务就开启多少线程,开启线程的个数是由系统内部决定的
  • 异步函数+串行队列

    • 会开启一条线程,队列中的任务是串行执行的- 同步函数+并发队列
    • 不会开启线程,任务是串行执行的
  • 同步函数+串行队列

    • 不会开启线程,任务是串行执行的
  • 主队列

    • 主队列是GCD自带的一种特殊的串行队列
    • 主队列的任务都是在主线程执行的
    • 主队列特点:如果主队列发现当前主线程有任务执行,那么主队列就会暂停调用队列中的任务,直到主线程空闲为止
    • 异步函数+主队列:都在主线程,并不会开启线程,任务是串行执行的
    • 同步函数+主队列:产生死锁(在子线程调用不会产生死锁)
  • GCD栅栏函数不能使用全局队列,必须手动创建队列

  • GCD快速迭代

    • GCD快速迭代会创建子线程,子线程和主线程同时执行任务,可以快速提升速度,里面的任务是同步执行的,任务的执行顺序是不定的
      //for循环是普通迭
      //快速迭代
      //第一个参数:遍历的次
      //第二个参数:并发队列
      //第三个参数:index 索引
      dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) 
      });
    
  • GCD队列组

  • NSOperation

    • NSOperation和NSoperationQueue实现多线程的步骤
      • 先将需要执行的操作封装到一个NSOperation对象中
      • 然后将NSOperation对象添加到NSoperationQueue中
      • 系统会自动将NSOperationQueue中的NSoperation取出来
      • 将取出的NSOperation封装的操作放到一条新线程中执行
  • 位移枚举

    • 可以同时传多个参数
  • RunLoop

    • 运行循环
    • 基本作用
      • 保持程序的执行运行
      • 处理App中的各种事件(触摸,定时器,)
      • 节省CPU资源,提高程序性能,该工作时工作,该休息时休息
    • 获取RunLoop对象
      • NSRunLoop
      • CFRunLoopRef
    • RunLoop与线程
      • 每条线程都有唯一一个对应的RunLoop对象
      • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建 currentRunLoop
      • RunLoop在第一次获取时创建,在线程结束时销毁
      NSLog(@"runloop%p",[NSRunLoop currentRunLoop]);
      NSLog(@"mainRunloop%p",[NSRunLoop mainRunLoop]);
      //Core
      NSLog(@"%p",CFRunLoopGetMain());
      NSLog(@"%p",CFRunLoopGetCurrent());
      NSLog(@"%p",[[NSRunLoop mainRunLoop] getCFRunLoop]);
      
    • RunLoop相关类
      • CFRunLoopRef
      • CFRunLoopModeRef
        • 代表RunLoop的运行模式
        • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source。Timer/Observer
        • 每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称为CurrentMode
        • 如果需要切换Mode,只能退出Loop,再重新指定Mode进入,使其相互不受影响
        • kCFRunLoopdefaultMode:App的默认Mode,通常主线程是在这个Mode下运行的
        • UITrickingRunLoopMode,界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不 受其他Mode影响
        • UIInitializationRunLoopMode,在刚启动App时进入的第一个Mode,启动完成后就不再使用
        • GSEventReceiveRunLoopMode:接受系统事件内部的Mode,通常用不到
        • kCFRunLoopCommonModes:这个是占位用的Mode,不是一种真正的Mode
      • CFRunLoopSourceRef
        • 事件源
        • Source0 非基于Port的,用户主动触发的
        • Souree1:基于Port的,系统触发的
      • CFRunLoopTimerRef
      • CFRunLoopObserverRef
        • 能够监听RunLoop状态的改变
        • 可以监听的点
          • 即将进入runloop
          • 即将处理Timer
          • 即将处理Source
          • 积极进入休眠
          • 刚从休眠中唤醒
          • 即将推出runloop
        • 说的
    • 在runloop中有多个运行模式,但是runloop只能选择一种模式运行,mode里面至少要有一个timer或者source
    • 栈的控件大小:子线程512kb,OS X的主线程是8MB,iOS的主线程是1MB子线程允许的最小堆栈大小是16 KB,堆栈大小必须是4 KB的倍数