当前位置: 首页 > news >正文

「iOS」——多线程原理总结

底层学习

  • 前言
  • 基本概念及原理
    • 线程、进程与队列
        • 线程的定义:
        • 进程的定义:
        • 线程与进程之间的联系与区别:
        • 线程和runloop的关系
        • 影响任务执行速度的因素
    • 多线程
      • 多线程生命周期
      • 线程池的原理
      • iOS中多线程的实现方式
      • 线程安全问题
        • 互斥锁
        • 自旋锁
      • 对比GCD和NSOperation
  • NSThread
  • GCD
    • 函数
    • 队列
        • 主队列 和 全局并发队列
      • 函数与队列的不同组合
        • 串行队列 + 同步派发
        • 串行队列 + 异步派发
        • 并发队列 + 同步派发
        • 并发队列 + 异步派发
        • 主队列 + 同步函数
        • 主队列 + 异步派发
        • 全局并发队列 + 同步函数
        • 全局并发队列 + 异步函数
        • 总结
      • dispatch_after
      • dispatch_once
      • dispatch_apply
      • dispatch_group_t
          • dispatch_group_async + dispatch_group_notify
        • dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
    • NSOperation
      • NSOperationQueue


前言

多线程在iOS的开发中起到了非常重要的作用,是IOS开发必须掌握的一环,现在开始进行一个简单的总结。

基本概念及原理

线程、进程与队列

线程的定义:
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被成为主线程UI线程
进程的定义:
  • 进程是指在系统中正在运行的一个应用程序,如微信、支付宝app都是一个进程
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

通俗地说,可以理解为:进程是线程的容器,而线程用来执行任务。在iOS中是单进程开发,一个进程就是一个app,进程之间是相互独立的,如支付宝、微信、qq等,这些都是属于不同的进程。

线程与进程之间的联系与区别:
  • 地址空间:同一进程线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉,所以多进程要比多线程健壮
  • 进程切换时,消耗的资源大、效率高.所以设计到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程
  • 线程是处理器调度的基本单位,但进程不是
  • 线程没有地址空间,线程包含在进程地址空间中
线程和runloop的关系
  • runloop与线程是一一对应的 —— 一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里

  • runloop是来管理线程的 —— 当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务

  • runloop在第一次获取时被创建,在线程结束时被销毁

    • 对于主线程来说,runloop在程序一启动就默认创建好了

    • 对于子线程来说,runloop是懒加载的 —— 只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调

影响任务执行速度的因素

以下因素都会对任务的执行速度造成影响:

  • cpu的调度
  • 线程的执行速率
  • 队列情况
  • 任务执行的复杂度
  • 任务的优先级

多线程

多线程生命周期

多线程的生命周期主要分为5部分:新建 - 就绪 - 运行 - 阻塞 - 死亡!

请添加图片描述

  • 新建:实例化线程对象
  • 就绪:线程对象调用start方法,将线程对象加入可调度线程池等待CPU的调用,即调用start方法,并不会立即执行,进入就绪状态,需要等待一段时间,经CPU调度后才执行,也就是从就绪状态进入运行状态
  • 运行:CPU 负责调度可调度线程池中线程的执行。在线程执行完成之前,其状态可能会在就绪和运行之间来回切换.就绪和运行之间的状态变化由CPU负责,程序员不能干预
  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)
  • 死亡:正常死亡,即线程执行完毕。非正常死亡,即当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)

处于运行中的线程拥有一段可以执行的时间(称为时间片):

  • 如果时间片用尽,线程就会进入就绪状态队列
  • 如果时间片没有用尽,且需要开始等待某事件,就会进入阻塞状态队列
  • 等待事件发生后,线程又会重新进入就绪状态队列
  • 每当一个线程离开运行,即执行完毕或者强制退出后,会重新从就绪状态队列选择一个线程继续执行

关于线程的exit和cancel方法:

  • exit:一旦强行终止线程,后续的所有代码都不会执行
  • cancel:取消当前线程,但是不能取消正在执行的线程

线程池的原理

可以看到主要分为以下四步:

  1. 判断核心线程池是否都正在执行任务:
    • 返回NO,创建新的工作线程去执行
    • 返回YES,进行第二步
  2. 判断线程池工作队列是否已经饱满:
    • 返回NO,将任务存储到工作队列,等待CPU调度
    • 返回YES,进入第三步
  3. 判断线程池中的线程是否都处于执行状态
    • 返回NO,安排可调度线程池中空闲的线程去执行任务
    • 返回YES,进入第四步
  4. 交给饱和策略去执行,主要有以下四种:
    • AbortPolicy:直接抛出RejectedExecutionExeception异常来阻止系统正常运行
    • CallerRunsPolicy:将任务回退到调用者
    • DisOldestPolicy:丢掉等待最久的任务
    • DisCardPolicy:直接丢弃任务

iOS中多线程的实现方式

iOS中多线程的实现方式主要有四种:pthread、NSThread、GCD、NSOperation

请添加图片描述

线程安全问题

当多个线程同时访问一块内存,容易引发数据错乱和数据安全问题,有以下两种解决方案:

  • 互斥锁(即同步锁):@synchronized
  • 自旋锁
互斥锁
  • 保证锁内的代码,同一时间,只有一条线程能够执行!
  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
  • 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠
  • 能够加锁的是任意 NSObject 对象,但必须是 NSObject 对象
  • 锁对象必须保证所有线程都能访问
  • 单点加锁时推荐使用 self
自旋锁
  • 自旋锁与互斥锁类似,但它不是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋)阻塞状态

  • 使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic,本身就有一把自旋锁

  • 加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能

  • atomic 本身就有一把锁(自旋锁)

iOS开发的建议:

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

对比GCD和NSOperation

GCDNSOperation的关系如下:

  • GCD是面向底层的C语言的API
  • NSOperation是用GCD封装构建的,是GCD的高级抽象

GCD和NSOperation的对比如下:

  • GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构 —— 写起来更加方便
  • GCD只支持FIFO的队列,而NSOpration可以设置最大并发数、设置优先级、添加依赖关系等调整执行顺序
  • NSOpration甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier任务才能控制执行顺序,较为复杂
  • NSOperation支持KVO(面向对象)可以检测operation是否正在执行、是否结束、是否取消(如果是自定义的NSOperation 子类,需要手动触发KVO通知)

NSThread

NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。

通过NSThread创建线程的方式主要有以下三种方式:

  • 通过init初始化方式创建
  • 通过detachNewThreadSelector构造器方式创建
  • 通过performSelector...方法创建,主要是用于获取主线程,以及后台线程
//1、创建
- (void)cjl_createNSThread{NSString *threadName1 = @"NSThread1";NSString *threadName2 = @"NSThread2";NSString *threadName3 = @"NSThread3";NSString *threadNameMain = @"NSThreadMain";//方式一:初始化方式,需要手动启动NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];[thread1 start];//方式二:构造器方式,自动启动[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];//方式三:performSelector...方法创建[self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];//方式四[self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];}
- (void)doSomething:(NSObject *)objc{NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}

属性

- thread.isExecuting    //线程是否在执行
- thread.isCancelled    //线程是否被取消
- thread.isFinished     //是否完成
- thread.isMainThread   //是否是主线程
- thread.threadPriority //线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高

NSThread常用的类方法有以下:

  • currentThread:获取当前线程
  • sleep...:阻塞线程
  • exit:退出线程
  • mainThread:获取主线程

请添加图片描述

GCD

GCD就是Grand Central Dispatch,它是纯 C 语言。

日常开发中,一般为以下形式:

 dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{NSLog(@"GCD基本使用");
});

GCD核心 主要是由 任务 + 队列 + 函数 构成

//********GCD基础写法********
//创建任务
dispatch_block_t block = ^{NSLog(@"hello GCD");
};//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);//将任务添加到队列,并指定函数执行
dispatch_async(queue, block);
  • 使用dispatch_block_t创建任务
  • 使用dispatch_queue_t创建队列
  • 将任务添加到队列,并指定执行任务的函数dispatch_async

注意

这里的任务是指执行操作的意思,在使用dispatch_block_t创建任务时,主要有以下两点说明

  • 任务使用block封装
  • 任务的block没有参数没有返回值

函数

GCD中执行任务的方式有两种,同步执行异步执行,分别对应同步函数dispatch_sync异步函数dispatch_async

  • 同步执行,对应同步函数dispatch_sync
    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程,即不具备开启新线程的能力
    • 在当前线程中执行block任务
  • 异步执行,对应异步函数dispatch_async
    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)
    • 异步是多线程的代名词

综上所述,两种执行方式的主要区别有两点:

  • 是否等待队列的任务执行完毕
  • 是否具备开启新线程的能力

队列

多线程中所说的队列Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列.队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取.每读取一个任务,则动队列中释放一个任务。而队列又分为串行队列并发队列

串行队列:每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程

并发队列:一次可以并发执行多个任务,即开启多个线程,并同时执行任务

请添加图片描述

  • 串行队列:每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行)

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);创建串行队列

    • 其中的DISPATCH_QUEUE_SERIAL也可以使用NULL表示,这两种均表示 默认的串行队列

// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.CJL.Queue", NULL);dispatch_queue_t serialQueue2 = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_SERIAL);
  • 并发队列:一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行)

    • 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列

    • 注意:并发队列的并发功能只有在异步函数下才有效

// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
主队列 和 全局并发队列
  • 主队列(Main Dispatch Queue):GCD中提供的特殊的串行队列

    • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建

    • 不会开启线程

    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

    • 使用dispatch_get_main_queue()获得主队列

    • 通常在返回主线程 更新UI时使用

    //主队列的获取方法
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 全局并发队列(Global Dispatch Queue):GCD提供的默认的并发队列

    • 为了方便程序员的使用,苹果提供了全局队列
    • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
    • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
      • 第一个参数表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在ios9之后,已经被服务质量(quality-of-service)取代
      • 第二个参数使用0
  //全局并发队列的获取方法dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);//优先级从高到低(对应的服务质量)依次为- DISPATCH_QUEUE_PRIORITY_HIGH       -- QOS_CLASS_USER_INITIATED- DISPATCH_QUEUE_PRIORITY_DEFAULT    -- QOS_CLASS_DEFAULT- DISPATCH_QUEUE_PRIORITY_LOW        -- QOS_CLASS_UTILITY- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND

全局并发队列 + 主队列 配合使用

在日常开发中,全局队列+并发并列一般是这样配合使用的

//主队列 + 全局并发队列的日常使用dispatch_async(dispatch_get_global_queue(0, 0), ^{//执行耗时操作dispatch_async(dispatch_get_main_queue(), ^{//回到主线程进行UI操作});});

函数与队列的不同组合

串行队列 + 同步派发

任务一个接一个地在当前线程执行,不会开辟新线程

请添加图片描述

串行队列 + 异步派发

任务一个接一个地执行,但是会开辟新线程

请添加图片描述

并发队列 + 同步派发

任务一个接一个地执行,不开辟线程

请添加图片描述

并发队列 + 异步派发

任务乱序进行并且会开辟新线程

请添加图片描述

主队列 + 同步函数

任务互相等待,造成死锁

·请添加图片描述

为什么这样会造成死锁,这里分析一下原因:

  • 主队列有两个任务,顺序为:NSLog任务 - 同步block

  • 执行NSLog任务后,执行同步Block,会将任务1(即i=1时)加入到主队列,主队列顺序为:NSLog任务 - 同步block - 任务1

  • 任务1的执行需要等待同步block执行完毕才会执行,而同步block的执行需要等待任务1执行完毕,所以就造成了任务互相等待的情况,即造成死锁崩溃

死锁

  • 主线程因为同步函数的原因等着先执行任务
  • 主队列等着主线程的任务执行完毕再执行自己的任务
  • 主队列和主线程相互等待会造成死锁
主队列 + 异步派发

主队列是一个特殊的串行队列,它虽然是串行队列,但是其异步派发不会开辟新线程,而是将任务安排到主线程的下一个运行循环(Run Loop)周期执行

请添加图片描述

全局并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟新线程

全局并发队列 + 同步函数

全局并发队列 + 异步函数

【任务乱序执行】:任务乱序执行,会开辟新线程

请添加图片描述

总结
函数\队列串行队列并发队列主队列全局并发队列
同步函数顺序执行,不开辟线程顺序执行,不开辟线程死锁顺序执行,不开辟线程
异步函数顺序执行,开辟线程乱序执行,开辟线程顺序执行,不开辟线程乱序执行,开辟线程

dispatch_after

dispatch_after表示在队列中的block延迟执行,确切地说是延迟将block加入到队列

- (void)cjl_testAfter{/*dispatch_after表示在某队列中的block延迟执行应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)*/dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"2s后输出");});}

dispatch_once

dispatch_once可以保证在app运行期间,block中的代码只执行一次,可以用来创建单例

- (void)cjl_testOnce{/*dispatch_once保证在App运行期间,block中的代码只执行一次应用场景:单例、method-Swizzling*/static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{//创建单例、method swizzled或其他任务NSLog(@"创建单例");});
}

dispatch_apply

dispatch_apply将指定的block追加到指定的队列中重复执行,并等到全部的处理执行结束(相当于线程安全的for循环)

应用场景:在拉取网络数据后提前计算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性

- (void)cjl_testApply{/*dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性- 添加到串行队列中——按序执行- 添加到主队列中——死锁- 添加到并发队列中——乱序执行- 添加到全局队列中——乱序执行*/dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);NSLog(@"dispatch_apply前");/**param1:重复次数param2:追加的队列param3:执行任务*/dispatch_apply(10, queue, ^(size_t index) {NSLog(@"dispatch_apply 的线程 %zu - %@", index, [NSThread currentThread]);});NSLog(@"dispatch_apply后");
}

dispatch_group_t

dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

dispatch_group_notify

  • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

dispatch_group_wait

  • 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1

  • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。

  • 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。

应用场景:多个接口请求之后刷新页面

dispatch_group_async + dispatch_group_notify

dispatch_group_notifydispatch_group_async执行结束之后会受收到通知

- (void)cjl_testGroup1{/*dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间应用场景:多个接口请求之后刷新页面*/dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_group_async(group, queue, ^{NSLog(@"请求一完成");});dispatch_group_async(group, queue, ^{NSLog(@"请求二完成");});dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"刷新页面");});//或者用下面这个:
// 等待所有任务完成
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
if (dispatch_group_wait(group, timeout) == 0) {// 所有数据获取完成,进行最终处理NSLog(@"All data fetched!");
} else {// 超时,可以执行一些错误处理逻辑NSLog(@"Timeout waiting for data");
}}

dispatch_group_notify 不同,dispatch_group_wait 是一个同步操作,会阻塞当前线程,直到所有任务完成或者超时。在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印 “All data fetched!”。如果超时,则打印 “Timeout waiting for data”。

dispatch_group_enter + dispatch_group_leave + dispatch_group_notify

dispatch_group_enterdispatch_group_leave成对出现,使进出组的逻辑更加清晰

- (void)cjl_testGroup2{/*dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰*/dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_group_enter(group);dispatch_async(queue, ^{NSLog(@"请求一完成");dispatch_group_leave(group);});dispatch_group_enter(group);dispatch_async(queue, ^{NSLog(@"请求二完成");dispatch_group_leave(group);});dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"刷新界面");});
}

在此基础上还可以使用 dispatch_group_wait

- (void)cjl_testGroup3{/*long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)group:需要等待的调度组timeout:等待的超时时间(即等多久)- 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕- 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕返回值:为long类型- 返回值为0——在指定时间内调度组完成了任务- 返回值不为0——在指定时间内调度组没有按时完成任务*/dispatch_group_t group = dispatch_group_create();dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_group_enter(group);dispatch_async(queue, ^{NSLog(@"请求一完成");dispatch_group_leave(group);});dispatch_group_enter(group);dispatch_async(queue, ^{NSLog(@"请求二完成");dispatch_group_leave(group);});//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));NSLog(@"timeout = %ld", timeout);if (timeout == 0) {NSLog(@"按时完成任务");}else{NSLog(@"超时");}dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"刷新界面");});
}

NSOperation

NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程

NSOperatino实现多线程的步骤如下:

  • 1、创建任务:先将需要执行的操作封装到NSOperation对象中。
  • 2、创建队列:创建NSOperationQueue。
  • 3、将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。
//基本使用
- (void)cjl_testBaseNSOperation{//处理事务NSInvocationOperation *op =  [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation::) object:@"CJL"];//创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];//操作加入队列[queue addOperation:op];}
- (void)handleInvocation:(id)operation{NSLog(@"%@ - %@", operation, [NSThread currentThread]);
}

需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:

  • 1、使用子类NSInvocationOperation
//直接处理事务,不添加隐性队列
- (void)cjl_createNSOperation{//创建NSInvocationOperation对象并关联方法,之后start。NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];[invocationOperation start];
}
  • 2、使用子类NSBlockOperation
- (void)cjl_testNSBlockOperationExecution{//通过addExecutionBlock这个方法可以让NSBlockOperation实现多线程。//NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"main task = >currentThread: %@", [NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"task1 = >currentThread: %@", [NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"task2 = >currentThread: %@", [NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"task3 = >currentThread: %@", [NSThread currentThread]);}];[blockOperation start];
}
  • 3、定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
//*********自定义继承自NSOperation的子类*********
@interface CJLOperation : NSOperation
@end@implementation CJLOperation
- (void)main{for (int i = 0; i < 3; i++) {NSLog(@"NSOperation的子类:%@",[NSThread currentThread]);}
}
@end//*********使用*********
- (void)cjl_testCJLOperation{//运用继承自NSOperation的子类 首先我们定义一个继承自NSOperation的类,然后重写它的main方法。CJLOperation *operation = [[CJLOperation alloc] init];[operation start];
}

NSOperationQueue

NSOperationQueue添加事务

NSOperationQueue有两种队列:主队列其他队列。其他队列包含了 串行和并发

  • 主队列:主队列上的任务是在主线程执行的。
  • 其他队列(非主队列):加入到’非队列’中的任务默认就是并发,开启多线程。
- (void)cjl_testNSOperationQueue{/*NSInvocationOperation和NSBlockOperation两者的区别在于:- 前者类似target形式- 后者类似block形式——函数式编程,业务逻辑代码可读性更高NSOperationQueue是异步执行的,所以任务一、任务二的完成顺序不确定*/// 初始化添加事务NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"任务1————%@",[NSThread currentThread]);}];// 添加事务[bo addExecutionBlock:^{NSLog(@"任务2————%@",[NSThread currentThread]);}];// 回调监听bo.completionBlock = ^{NSLog(@"完成了!!!");};NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:bo];NSLog(@"事务添加进了NSOperationQueue");
}

设置执行顺序

//执行顺序
- (void)cjl_testQueueSequence{NSOperationQueue *queue = [[NSOperationQueue alloc] init];for (int i = 0; i < 5; i++) {[queue addOperationWithBlock:^{NSLog(@"%@---%d", [NSThread currentThread], i);}];}
}

设置优先级

- (void)cjl_testOperationQuality{/*NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定全部先完成- 不使用sleep——高优先级的任务一先于低优先级的任务二- 使用sleep进行延时——高优先级的任务一慢于低优先级的任务二*/NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 5; i++) {//sleep(1);NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);}}];// 设置最高优先级bo1.qualityOfService = NSQualityOfServiceUserInteractive;NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 5; i++) {NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);}}];// 设置最低优先级bo2.qualityOfService = NSQualityOfServiceBackground;NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:bo1];[queue addOperation:bo2];}

请添加图片描述

设置并发数

//设置并发数
- (void)cjl_testOperationMaxCount{/*在GCD中只能使用信号量来设置并发数而NSOperation轻易就能设置并发数通过设置maxConcurrentOperationCount来控制单次出队列去执行的任务数*/NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.name = @"Felix";queue.maxConcurrentOperationCount = 2;for (int i = 0; i < 5; i++) {[queue addOperationWithBlock:^{ // 一个任务[NSThread sleepForTimeInterval:2];NSLog(@"%d-%@",i,[NSThread currentThread]);}];}
}

添加依赖(先后顺序)

//添加依赖
- (void)cjl_testOperationDependency{NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"请求token");}];NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"拿着token,请求数据1");}];NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{[NSThread sleepForTimeInterval:0.5];NSLog(@"拿着数据1,请求数据2");}];[bo2 addDependency:bo1];[bo3 addDependency:bo2];[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];NSLog(@"执行完了?我要干其他事");
}

线程间通讯

//线程间通讯
- (void)cjl_testOperationNoti{NSOperationQueue *queue = [[NSOperationQueue alloc] init];queue.name = @"Felix";[queue addOperationWithBlock:^{NSLog(@"请求网络%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);[[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);}];}];}
http://www.dtcms.com/a/298109.html

相关文章:

  • Codeforces Round 735 (Div. 2) D. Diane
  • 页面实时数据更新(进入页面或者浏览器后退前进状态刷新页面)
  • 单目云台是一种安防监控设备,它通常配备一个摄像机,用于实现远程监控和目标追踪
  • nacos的配置中心
  • MySQL性能优化配置终极指南
  • 算法讲解--有效三角形的个数
  • 将本地项目推送到远程github仓库
  • Promise的allSettled,all,race
  • RCE真实漏洞初体验
  • CGA老年综合评估汉密尔顿抑郁量表与认知评估联用
  • HTML 常用标签速查表
  • 智能机器人的技术革命:从感知到决策的全栈架构解析
  • 目前市面上arm64-v8a、armeabi-v7a设备的市占率有多少?为什么x86架构的手机越来越少?
  • 如何识别发票特殊版式?OCR大模型如何颠覆传统并保证准确率?
  • 力扣面试150(41/150)
  • 力扣-560.和为K的子数组
  • 第 9 篇:神经网络初探——当AI拥有了“大脑”,世界从此不同
  • JAVA语法糖
  • 高效算法的实现与优化是计算机科学的核心,直接决定了程序的性能和资源消耗。下面针对排序算法、搜索算法和动态规划,深入探讨其高效实现与关键优化技术。
  • 机器视觉对位印刷加工PCB板应用
  • LlamaIndex 和 Elasticsearch Rerankers:无与伦比的简洁
  • Power Compiler:漏电功耗、内部功耗、切换功耗及其计算方式(NLPM)
  • 基于Transform、ARIMA、LSTM、Prophet的药品销量预测分析
  • Jenkins中HTML文件显示样式问题解决方案
  • 【数据库】AI驱动未来:电科金仓新一代数据库一体机如何重构性能边界?
  • Vue接口平台十二 —— 测试任务(Task)
  • CentOS7 安装 Redis
  • ThreadLocal使用及其原理和注意点
  • 背包DP之完全背包
  • MCP (Model Context Protocol) 与 HTTP API:大模型时代的通信新范式