iOS多线程

pthread

pthread基于C开发的一套通用的多线程API,适用于Unix/Linux/Windows/Mac OS X等系统.
具有跨平台可移植性的特点,线程生命周期需要程序员自己管理,开发难度大,开发中几乎不再使用.

NSThread

  • 1.创建

      // 方法1 
      + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
      // 方法2    iOS10.0 API
      + (void)detachNewThreadWithBlock:(void (^)(void))block;
      // 方法3
      // 创建线程
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(thread:) object:@"thread"];
      // 线程生命周期是:线程启动,thread:方法完成后线程才会死掉 
      //我们实例化的NSThread对象就是一条线程.
      [thread start];
      
      //或者 (ios(10.0))
      //NSThread  *thread= [[NSThread alloc]initWithBlock:^{
      }];
      /*方法1和2简单快捷,创建后就会执行,不许手动开启,方法3实例化线程对象,可以拿到线程.创建后需要手动开启线程.*/
    
  • 2.实例化对象的属性和方法

    • 属性

        //实例化线程对象获取
        /*线程字典
        可以在线程里面的任何地方被访问。
        使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。
        */
        @property (readonly, retain) NSMutableDictionary *threadDictionary;
      
        /** NSQualityOfService:优先级  threadPriority属性已废弃
        NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上,这些任务需要在一瞬间完成
        NSQualityOfServiceUserInitiated:次高优先级,由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
        NSQualityOfServiceDefault:默认优先级,介于UserInteractive 和 Utility,当没有设置优先级的时候,线程默认优先级
        NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务,比如下载的任务
        NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务,比如后台进行备份的操作
        */
        // 线程开始后为只读属性,不可更改,需在实例化后为开启前设置
        @property NSQualityOfService qualityOfService API_AVAILABLE(ios(8.0)); 
      
        //线程名称
        @property (nullable, copy) NSString *name;
      
        //线程使用栈区大小,默认是512K
        @property NSUInteger stackSize
      
        // reports whether current thread is main
        @property (class, readonly) BOOL isMainThread;
      
        //线程是否正在执行
        @property (readonly, getter=isExecuting) BOOL executing;
        //线程是否已经完成
        @property (readonly, getter=isFinished) BOOL finished;
        //线程是否已经取消
        @property (readonly, getter=isCancelled) BOOL cancelled;
      
    • 对象方法

        //取消线程
        - (void)cancel;
        //开启线程
        - (void)start;
        //线程的入口函数
        - (void)main 
      
    • 三个通知

        1.NSDidBecomeSingleThreadedNotification  
        没有什么实际的处理工作,可暂时忽略;
        
        2.NSThreadWillExitNotification
        该通知是在进程执行exit方法的时候触发的,
        这个通知被发送到通知中心的时候,
        不包含userinfo的字典信息.通过监听这个通知来处理进程即将结束之前的操作.
      
        3.NSWillBecomeMultiThreadedNotification
        该通知只会被NSThread触发一次,
        条件是当第一个进程在调用了start或者detachNewThreadSelector:toTarget:withObject:方法.
        该通知的接收是在主线程上进行.
      
    • 分类 NSThreadPerformAdditions (隐式创建&线程间通讯)

        /*
        指定方法在主线程中执行
        参数:
        1.SEL 方法
        2.方法参数
        3.是否等待当前执行完毕
        4.指定的Runloop model
        */
        - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
        // 等价于上一个方法modes为kCFRunLoopCommonModes
        - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
        
        /*
        指定方法在某个线程中执行
        参数:
        1.SEL 方法
        2.方法参数
        3.是否等待当前执行完毕
        4.指定的Runloop model
        */
        - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
        // 等价于上一个方法modes为kCFRunLoopCommonModes 
        - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
        
        /*
        指定方法在开启的子线程中执行   后台完成任务
        参数:
        1.SEL 方法
        2.方法参数
        */
        - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
      
    • 类方法

        @property (class, readonly, strong) NSThread *mainThread;//获取到主线程
        @property (class, readonly, strong) NSThread *currentThread;//获取当前线程
        @property (class, readonly) BOOL isMainThread;//当前是否是主线程
        + (BOOL)isMultiThreaded;//当前线程是否是多线程
        + (void)sleepUntilDate:(NSDate *)date;//当前线程休眠到某时刻
        + (void)sleepForTimeInterval:(NSTimeInterval)ti;//当前线程休眠时长
        + (void)exit;//退出当前线程
        + (double)threadPriority;//当前线程的优先级
        + (BOOL)setThreadPriority:(double)p;//设置当前线程的优先级
      

    NSThread使用较简单,可以直接操作线程,生命周期需要程序员自己管理,使用频率不高

GCD

百度百科:Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。

  • 1.GCD的任务和队列

    • 任务:所要执行的操作,在GCD中,就是block中所要执行的代码.

      1.同步任务:不具备开启新线程的能力,只能在当前线程执行,并且被添加在该线程的任务需要按顺序依次执行.
      2.异步任务:具备开启新线程的能力,异步添加任务到指定的队列中,会被立即执行,不需要等待.

    • 队列:是执行任务的等待队列,即用来存放任务的队列.遵循FIFO原则,就像火车过山洞,先进山洞的会先出.

      串行队列:被添加的任务会按照顺序依次执行,每次只会有一个任务执行.只开启一个线程,一个任务执行完毕,接着执行下一个任务.
      并发队列:开启多个线程,可以同时执行多个任务.即使用多个线程同时处理多个任务.

  • 2.任务的创建

      // 同步执行任务创建方法
      dispatch_sync(queue, ^{
      	// 这里放同步执行任务代码
      });
      // 异步执行任务创建方法
      dispatch_async(queue, ^{
      	// 这里放异步执行任务代码
      });
    
  • 3.队列的创建

      /**
      params 1:队列的唯一标识符,可为空;
      params 2:串行或并发队列
      */
      // 串行队列的创建方法
      dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);
      // 并发队列的创建方法
      dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
    
      //两个特殊的队列
      //1.主队列(Main Dispatch Queue) :一种特殊的串行队列.所有放在主队列中的任务,都会放到主线程中执行。
      dispatch_queue_t queue = dispatch_get_main_queue()
      //2.全局并发队列(Global Dispatch Queue):特殊的并发队列.
      
      /**
      params 1:队列优先级 一般传DISPATCH_QUEUE_PRIORITY_DEFAULT;
      params 2:预留参数,暂时传0.
      */
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
  • 4.任务与队列的组合

    1.同步串行队列:不开启新线程,串行执行任务;
    2.同步并发队列:不开启新线程,串行执行任务;
    3.同步主队列:如果在主线程调用,造成死锁;如果在其他线程调用,不开启新线程,串行执行,在主线程串行执行任务;
    4.异步串行队列:开启新线程,串行执行任务;
    5.异步并发队列:开启新线程,并发执行任务;
    6.异步主队列:不开启新线程,在主线程串行执行任务

  • 5.线程间通讯

    iOS中,我们一般把UI刷新放在主线程进行,把数据请求,图片加载等耗时操作放在其他线程,当耗时操作完成后,我们需要通知主线程刷新数据.

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      	// 异步执行耗时操作
      	dispatch_async(dispatch_get_main_queue(), 0), ^{
      		//通知主线程刷新UI
      	});
      });
    
  • 6.GCD的其他方法

    • 6.1 dispatch_barrier_async 与 dispatch_barrier_sync 栅栏方法

      dispatch_barrier_async:提交用于在并发队列上异步执行的屏障块。仅与DISPATCH_QUEUE_CONCURRENT队列相关

        //dispatch_barrier_async
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        	dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
        	NSLog(@"start-----%@",[NSThread currentThread]);
        	dispatch_async(queue, ^{
        		NSLog(@"1-----%@",[NSThread currentThread]);
        	});
      
        	dispatch_async(queue, ^{
        		NSLog(@"2-----%@",[NSThread currentThread]);
        	});
      
        	dispatch_barrier_async(queue, ^{
        		NSLog(@"barrier-----%@",[NSThread currentThread]);
        	});
      
        	NSLog(@"ongoing-----%@",[NSThread currentThread]);
        	dispatch_async(queue, ^{
        		NSLog(@"4-----%@",[NSThread currentThread]);
        	});
        	dispatch_async(queue, ^{
        		NSLog(@"5-----%@",[NSThread currentThread]);
        	});
        	NSLog(@"finish-----%@",[NSThread currentThread]);
        });
        /**
        打印结果:
        第一次:
        start-----<NSThread: 0x600001548080>{number = 3, name = (null)}
        2-----<NSThread: 0x60000155d480>{number = 5, name = (null)}
        ongoing-----<NSThread: 0x600001548080>{number = 3, name = (null)}
        1-----<NSThread: 0x600001547d40>{number = 4, name = (null)}
        finish-----<NSThread: 0x600001548080>{number = 3, name = (null)}
        barrier-----<NSThread: 0x600001547d40>{number = 4, name = (null)}
        4-----<NSThread: 0x600001547d40>{number = 4, name = (null)}
        5-----<NSThread: 0x600001548080>{number = 3, name = (null)}
      
        第二次:
        start-----<NSThread: 0x600000bc3040>{number = 3, name = (null)}
        ongoing-----<NSThread: 0x600000bc3040>{number = 3, name = (null)}
        2-----<NSThread: 0x600000be16c0>{number = 5, name = (null)}
        1-----<NSThread: 0x600000be1600>{number = 4, name = (null)}
        finish-----<NSThread: 0x600000bc3040>{number = 3, name = (null)}
        barrier-----<NSThread: 0x600000be1600>{number = 4, name = (null)}
        4-----<NSThread: 0x600000be1600>{number = 4, name = (null)}
        5-----<NSThread: 0x600000bc3040>{number = 3, name = (null)}
        */
        !!!结果比较:在当前线程,不会等待dispatch_barrier_async和他之前的任务完成,代码会接着往下走.但是,dispatch_barrier_async会在1,2和4,5任务之间起到栅栏作用.并且,dispatch_barrier_async开启了新线程执行.
      

      dispatch_barrier_sync:提交用于在并发队列上同步执行的屏障块。仅与DISPATCH_QUEUE_CONCURRENT队列相关

        //dispatch_barrier_sync
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        	dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
        	NSLog(@"start-----%@",[NSThread currentThread]);
        	dispatch_async(queue, ^{
        		NSLog(@"1-----%@",[NSThread currentThread]);
        	});
      
        	dispatch_async(queue, ^{
        		NSLog(@"2-----%@",[NSThread currentThread]);
        	});
      
        	dispatch_barrier_sync(queue, ^{
        		NSLog(@"barrier-----%@",[NSThread currentThread]);
        	});
      
        	NSLog(@"ongoing-----%@",[NSThread currentThread]);
        	dispatch_async(queue, ^{
        		NSLog(@"4-----%@",[NSThread currentThread]);
        	});
        	dispatch_async(queue, ^{
        		NSLog(@"5-----%@",[NSThread currentThread]);
        	});
        	NSLog(@"finish-----%@",[NSThread currentThread]);
        });
        /**
        打印结果:
        第一次:
        start-----<NSThread: 0x600001170080>{number = 3, name = (null)}
        2-----<NSThread: 0x600001145a40>{number = 5, name = (null)}
        1-----<NSThread: 0x600001164200>{number = 4, name = (null)}
        barrier-----<NSThread: 0x600001170080>{number = 3, name = (null)}
        ongoing-----<NSThread: 0x600001170080>{number = 3, name = (null)}
        finish-----<NSThread: 0x600001170080>{number = 3, name = (null)}
        5-----<NSThread: 0x600001164200>{number = 4, name = (null)}
        4-----<NSThread: 0x600001145900>{number = 6, name = (null)}
      
        第二次:
        start-----<NSThread: 0x600003f5ed80>{number = 3, name = (null)}
        1-----<NSThread: 0x600003f5d880>{number = 4, name = (null)}
        2-----<NSThread: 0x600003f4c140>{number = 5, name = (null)}
        barrier-----<NSThread: 0x600003f5ed80>{number = 3, name = (null)}
        ongoing-----<NSThread: 0x600003f5ed80>{number = 3, name = (null)}
        finish-----<NSThread: 0x600003f5ed80>{number = 3, name = (null)}
        5-----<NSThread: 0x600003f4c140>{number = 5, name = (null)}
        4-----<NSThread: 0x600003f58280>{number = 6, name = (null)}
        */
        !!!结果对比:dispatch_barrier_sync和start/ongoing/finish在当前线程执行,dispatch_barrier_sync前的两个异步任务并发执行,完成后执行dispatch_barrier_sync任务(在当前线程执行),dispatch_barrier_sync任务完成后,异步执行其后面的任务.
      
    • 6.2 dispatch_after:GCD 延时执行方法

      调度一个任务(代码块),以便在指定时间在给定队列上执行。

        /**
        params 1:time
        params 2:dispatch_queue 队列
        params 3:要执行的block
        */
        //在主线程中延迟6秒执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        	NSLog(@"hello world");
        });
        //6秒后,将任务添加到主队列执行.dispatch_after在不要求十分精确情况下可以达到效果.如果要求十分精确,我们可以使用GCD定时器.
      
    • 6.3 dispatch_once:GCD 一次性代码&单例设计

      在程序中如果有只需要执行一次的代码,可使用dispatch_once,由于只会执行一次任务,所以也可用来创建单例.

        static dispatch_once_t onceToken; // 需要静态变量
        dispatch_once(&onceToken, ^{
        	// 只执行1次的代码(这里面默认是线程安全的)
        });
      
    • 6.4 dispatch_group 线程组

      当我们需要执行两个以上的耗时操作,并且需要这些耗时操作都完成之后再回到主线程执行任务,我们就需要用到线程组.
      当一个任务被放进队列时,我们调用dispatch_group_enter方法,加入到线程组,未完成任务+1;当任务完成后我们调用dispatch_group_leave方法,表示任务完成,移除任务,未完成任务-1;当线程组未完成任务为0时,dispatch_group_notify监听所有耗时任务全部完成,追加任务到线程组中并调用dispatch_group_notify代码块中的代码.

NSOperation 与 NSOperationQueue

NSOperation 与 NSOperationQueue是对GCD的封装,更好的面向对象,比GCD使用更加方便快捷.NSOperation是一个抽象类,可以为子类提供有用且线程安全的建立状态,优先级,依赖和取消等操作,我们可以直接使用他的两个实体类:NSBlockOperation与NSInvocationOperation直接创建操作,也可以自定义封装继承自NSOperation的类来实现操作.

属性和方法
  • 1.NSOperation

      // 取消操作方法
      - (void)cancel; //已执行的操作无法取消,对于未执行的操作实质是将该操作标记为 isCancelled状态。
    
      // 判断操作状态方法
      // 操作是否已经结束
      - (BOOL)isFinished; 
      // 操作是否已经标记为取消
      - (BOOL)isCancelled;
      // 操作是否正在在运行
      - (BOOL)isExecuting;
      // 操作是否处于准备就绪状态,返回值和操作的依赖关系相关
      - (BOOL)isReady;
      //阻塞当前线程,直到该操作结束
      - (void)waitUntilFinished;
    
  • 2.NSOperationQueue

      // 取消队列的所有操作
      - (void)cancelAllOperations;
      // 判断队列是否处于暂停状态
      @property (getter=isSuspended) BOOL suspended;
      // 阻塞当前线程,直到队列中的操作全部执行完毕
      - (void)waitUntilAllOperationsAreFinished;
      // 队列添加操作数组 wait:传Yes表示阻塞当前线程直到所有操作结束;NO相反
      - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
      // 当前在队列中的操作数组(操作执行结束后会自动从数组中清除)
      @property (readonly, copy) NSArray *operations;
      // 当前队列中的操作数。
      @property (readonly) NSUInteger operationCount;
      // 当前操作
      //@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue
    
使用
  • 1.NSOperation操作与NSOperationQueue操作队列

    类似GCD的任务与对列,NSOperation的子类创建操作,NSOperationQueue创建操作队列

    • 操作

        // 1.子类NSBlockOperation
        //创建操作,获取操作对象
        + (instancetype)blockOperationWithBlock:(void (^)(void))block;
        //添加操作
        - (void)addExecutionBlock:(void (^)(void))block;
      
        // 1.创建 NSBlockOperation 对象
        NSBlockOperation *opBlock = [NSBlockOperation blockOperationWithBlock:^{
        	//需要执行的操作
        }];
        //添加其他操作
        [opBlock addExecutionBlock:^{
        	//需要执行的操作
        }];
        [opBlock start];//调用 start 方法开始执行操作
        -------------------------------------------------------------------------
        // 2.子类NSInvocationOperation
        // 创建 NSInvocationOperation 对象
        NSInvocationOperation *opInvocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation) object:nil];
        // 2.调用 start 方法开始执行操作
        [opInvocation start];
      
    • 操作队列

        //主队列 mainQueue
        NSOperationQueue *queue = [NSOperationQueue mainQueue];
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        	[NSThread sleepForTimeInterval:2.0];
        	NSLog(@"1++++++++%@",[NSThread currentThread]);
        }];
      
        [op addExecutionBlock:^{
        	[NSThread sleepForTimeInterval:2.0];
        	NSLog(@"2++++++++%@",[NSThread currentThread]);
        }];
      
        [queue addOperation:op];
        /**
        1++++++++<NSThread: 0x600001371400>{number = 1, name = main}
        2++++++++<NSThread: 0x60000133ecc0>{number = 3, name = (null)}
        */
        //添加到主队列的操作会在主线程执行,使用addExecutionBlock:添加的额外操作有可能会在其他线程执行
      
        //自定义队列,
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        //添加到这种队列的操作会开启新线程,执行操作
        /**
        这种操作队列我们通过maxConcurrentOperationCount最大并发数来控制串行和并发,最大并发数默认是-1.
        当maxConcurrentOperationCount = 1时为串行队列;
        当maxConcurrentOperationCount > 1时,为并发队列,该数值设置超过系统支持时会以系统为准.
        */
      
    • 操作依赖

      //添加操作依赖
      - (void)addDependency:(NSOperation *)op;
      //移除操作依赖
      - (void)removeDependency:(NSOperation *)op;

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        NSBlockOperation *opBlock2 = [NSBlockOperation blockOperationWithBlock:^{
        	NSLog(@"2------%@", [NSThread currentThread]);
        }];
        NSBlockOperation *opBlock1 = [NSBlockOperation blockOperationWithBlock:^{
        	NSLog(@"1------%@", [NSThread currentThread]);
        }];
        [opBlock2 addDependency:opBlock1]; // 让opBlock2 依赖于 opBlock1,则先执行opBlock1,在执行opBlock2
        [queue addOperation:opBlock1];
        [queue addOperation:opBlock2];
      
    • 线程通讯

        //获取主线程:[NSOperationQueue mainQueue]
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        	//回主线程刷新UI事件
        }];
      

GCD几个面试代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值