【iOS】多线程以及GCD和NSOperation

本文详细介绍了iOS中多线程的基础知识,包括线程、进程和线程安全,讲解了苹果的GCD技术,包括任务、队列、线程间通信和队列组的概念,以及如何结合使用。此外,还探讨了NSOperation的使用,包括基本操作、队列执行和任务管理,强调了NSOperation与GCD的区别和优势。

线程基础

现代的操作系统,包括iOS,都支持线程(Thread)的概念。每个进程可以包含多个线程,借助多个CPU的内核,可以同时处理多个任务。即便是在早期单核CPU的时代,操作系统也可以把CPU资源在多个线程之间进行切换,给用户的感觉是在“同时”处理多个任务。

进程与线程

每个应用程序都有一个进程。例如在Mac系统中,可以通过活动监视器来查看当前Mac正在运行的进程。每个进程可以包含多个线程,这些线程可以同时运行,并各自处理一些任务。早期的CPU只有一个核心,因此会交替处理各个线程中的任务,让用户产生一种多任务同时处理的“感觉”。当CPU拥有多个内核时,各个线程就可以分配到不同的CPU内核中进行处理,即为真正意义上的多任务同时处理,如图所示:
在这里插入图片描述
对于任意一个iOS应用,程序运行起来后,默认会产生一个主线程(MainThread)。主线程专门用来处理UIKit对象的操作,如界面的显示与更新、处理用户交互事件等(请牢记这点,所有与UI相关的操作都要在主线程中进行)。

对于一个应用来说,之所以引入多个线程,很大程度上是由于存在一些操作是非常耗时的,例如:发送网络请求并等待服务器的响应,这种耗时操作是不能够放在主线程中进行操作的,因为在等待的时间内主线程被使用,用户是不能做任何交互动作的,因而会极大影响用户体验。对于耗时的操作,需要再另外创建一个线程,放到后台处理,当处理完成得到结果后,再返回主线程去设置UI界面,这就涉及线程间通信的相关知识。

多线程同时处理任务,还会涉及线程安全(thread-safe)的问题,当多个线程对一个对象进行同时操作时,就会影响结果的正确性。因此,线程对某个对象操作时,需要使用到“锁”的机制,即当一个线程操作一个对象的过程中,会给这个对象上锁,其他线程就不能够访问该对象了。加锁虽然解决了线程安全的问题,但带来的另外一个弊端就是影响了程序运行的效率。

当给一个自定义类中添加属性时,属性关键字其中就有atomicnonatomic的区分,其中,atomic是线程安全的,当有线程访问这个属性时,会为该属性的setter方法加锁,atomic是默认值。但是在实际的开发中,都会把给属性设置nonatomic关键字,因为对于移动设备来说,效率更加重要,但也需要程序员注意线程安全问题。

NSThread类

NSThred类是苹果官方提供的管理线程的类,提供了一些线程管理的方法。但是随着GCD和NSOperation的推出,NSThread的使用场景已经大大减少。在实际的开发中,偶尔会使用NSThread类来获取一些线程信息。常用的一些方法如下:

  • 获取当前线程对象。
+ (NSThread *) currentThread;
  • 获取主线程对象。
+ (NSThread *) mainThread;
  • 使主线程休眠ti秒。
+ (void)sleepForTimeInterval:(NSTimeInterval)ti

GCD

GCD(Grand Central Dispatch)技术,苹果首先应用于OS X中,随后在iOS中也引入了GCD技术。特别是苹果公司把CPU升级为多核后,GCD的使用变得更加广泛和重要。相比于NSThread,GCD中已经完全屏蔽了有关线程的概念,而是引入了任务和队列,把任务放到队列中执行,指定任务和队列的类型。其他有关线程管理的事务完全交由GCD来处理,大大简化了多任务开发的难度。

认识GCD

GCD是苹果公司推出的专门用于简化多线程编程的技术。在GCD中,程序员已经不再需要去关心有关线程的操作(如:线程创建、线程销毁、线程调度),而是引入了任务和队列两个核心概念。

由于GCD是苹果公司推出的技术,因此GCD能够很好地调度苹果设备的CPU资源,不论是在Mac平台,还是在iOS平台。特别是在iPhone中引入多核CPU之后,GCD的使用就变得越发重要。

由于GCD对线程管理进行了封装,因此,当工程师使用GCD时,只需要把任务(通常封装在一个Block中)添加到一个队列中执行,有关线程调度的工作完全交由GCD完成。

在使用GCD处理多任务执行时,只要按照如下步骤执行即可。

  • 在Block中定义需要执行的任务内容。
  • 把任务添加到队列queue中。

GCD对队列中的任务,按照“先进先出”的原则,根据任务添加到队列的顺序来对队列进行处理,GCD会根据任务和队列的类型,自动在多个线程之间分配工作。

任务

在GCD中,需要处理的事务统一使用Block封装起来,称为任务。任务有两种类型,同步任务和异步任务。通过调用不同的函数,来设置任务的类型。同时,任务编写在函数的Block参数中。

异步任务:执行任务时,会在另外的线程中执行,即可能会创建新的线程。

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

同步任务:执行任务时,会在当前的线程中执行,当前线程有可能是主线程,也有可能是子线程。

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

队列

GCD中,队列是一个重要概念。系统提供了若干预定义的队列,其中包括可以获取应用程序的主队列(任务始终在主线程上执行,与更新UI相关的操作必须在主队列中完成)。另外,工程师可以自由创建不同类型的队列,例如:并行队列和串行队列,队列的类型决定了任务的执行方式。GCD队列严格按照“先进先出”的原则,添加到GCD队列中的任务,始终会按照加入队列的顺序被执行。

并行队列:并行队列中的任务可以在多个线程之间分配执行,分配的原则由GCD控制,因此,并行队列中的任务,虽然执行时按照先进先出进行分配的,但由于各个任务被分配到不同的线程执行,因此其完成时间有可能不同,即:后分配的任务有可能先执行完成;并发队列一定需要和异步执行的任务(使用dispatch_async())结合起来使用才有意义。

串行队列:串行队列中的任务是按照顺序一个一个完成的,当一个任务完成后,才去执行下一个任务;因此,串行队列对应一个线程执行。

主队列:主队列也是一个串行队列,主队列中的任务都在主线程中执行。

程序员可以通过如下的函数来创建不同类型的队列。

  • 获取系统定义的并行队列。一般来说,开发中通常不需要自己创建并行队列,使用系统提供的即可。
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
  • 创建队列。可以创建并行队列,也可以创建串行队列。该方法常用于创建串行队列。
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr);
  • 获取主队列。
dispatch_get_main_queue(void);

队列与任务结合

在GCD中存在队列和任务两个核心概念,同时队列又分为并行队列、串行队列和主队列,任务包括异步任务和同步任务。GCD的使用方法就是把任务放到队列中执行,因而根据不同的任务类型和队列类型,就会存在6种组合。

  1. 异步任务+并行队列
    把异步任务放到并行队列进行执行,异步任务会在不同的线程中执行,这是最常使用的一种组合。
- (void)viewDidLoad {
   
   
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //获取一个并行队列,其队列优先级为默认,保留参数置为0
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建异步任务,并放到并行队列中执行
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(@"task1:%d", i);
        }
        NSLog(@"task1----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(@"task2:%d", i);
        }
        NSLog(@"task2----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(@"task3:%d", i);
        }
        NSLog(@"task3----%@", [NSThread currentThread]);
    });
}

运行结果如图所示。异步任务+并行队列组合情况下,每个任务会在不同的线程中同时执行。

在这里插入图片描述

  1. 异步任务+串行队列
    对于异步任务放在串行队列中执行时,任务只会在一个新开的线程中,按照顺序进行执行。
- (void)viewDidLoad {
   
   
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //获取一个串行队列,其字符串标识符为com.1101iOS,枚举类型设为NULL会创建一个serial队列
    dispatch_queue_t queue = dispatch_queue_create("com.1101iOS", NULL);
    //创建异步任务,并放到串行队列中执行
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(@"task1:%d", i);
        }
        NSLog(@"task1----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(@"task2:%d", i);
        }
        NSLog(@"task2----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
   
   
        for (int i = 0; i < 2; i++) {
   
   
            NSLog(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值