Objective-C基础笔记整理(三)多线程篇

  • 多线程
    • 1、基础概念
    • 2、多线程实现方案
      • 2.1、pthread
      • 2.2、NSTread
      • 2.3、GCD
      • 2.4、NSOperation


多线程


1、基础概念


  • 进程:在系统中正在运行的一个应用程序,例如打开常用的一个软件,系统会启动一个进程,每个线程之间是相互独立的。
  • 线程:一个进程要想执行任务,必须至少有一条线程(主线程),线程是进程执行任务的最小单位。
  • 线程的串行:一个线程只能执行一个任务,如果要执行多个任务,那么只能一个一个地按顺序执行这些任务。
  • 多线程:一个进程可以开启多条线程,每条线程可以同时执行不同的任务。

多线程的原理

1、CPU只能处理一条线程,只能在一条线程中工作。
2、多线程并发,其实是CPU快速的在多条线程之间的调度。
3、如果开启的线程过多会消耗大量的CPU资源,降低执行效率。
4、一般开启 3 ~ 5 个线程。


2、多线程实现方案


  • pthread:通用的多线程API,适用于Unix、Linux、Windows等系统,跨平台、可移植,使用难度大,C语言,线程生命周期由程序员管理。
  • NSTread:面向对象,简单易用,可直接操作线程对象,OC语言,线程生命周期由程序员管理。
  • GCD:开发中常用的一种方案,替代NSTread等线程技术,充分利用设备的多核,C语言,线程生命周期自动管理。
  • NSOperation:底层是GCD,比GCD多了一些更简单实用的功能,使用更加面向对象,OC语言,线程生命周期自动管理。

2.1、pthread


创建线程对象

    pthread_t thread;

创建线程

    /*
     第一个参数 线程对象
     第二个参数 线程属性
     第三个参数 要执行的任务
     第四个参数 函数的参数
     */
    pthread_create(&thread, NULL, run ,NULL);

设置子线程的状态设置为detach,该线程运行结束后会自动释放所有资源。

    pthread_detach()

执行方法

    void *run(void *param)
{
    for (NSInteger i = 0; i < 10000; i++) {
        NSLog(@"%zd", i);
    }
    return NULL;
}


2.2、NSTread


创建线程

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

设置线程名字

thread.name = @"running";

qualityOfService 设置优先级

    /*
     NSQualityOfServiceUserInteractive: 最高:主要用于与UI交互的操作,各种事件处理以及绘制图像等。
     NSQualityOfServiceUserInitiated: 次高:执行一些明确需要立即返回结果的任务。
     NSQualityOfServiceDefault: 默认:默认的优先级,介于次高级和普通级之间。
     NSQualityOfServiceUtility: 普通:用于执行不许要立即返回结果、耗时的操作,下载或者一些媒体操作等。
     NSQualityOfServiceBackground: 后台:后台执行一些用户不需要知道的操作,它将以最有效的方式运行。例如一些预处理的操作,备份或者同步数据等等。
     */
    thread.qualityOfService = NSQualityOfServiceUserInitiated;

开启线程

[thread start];

其它常用方法

 //判断是否是多线程
 + (BOOL)isMultiThreaded;
 //线程休眠
 + (void)sleepUntilDate:(NSDate *)date;
 //线程休眠时间
 + (void)sleepForTimeInterval:(NSTimeInterval)ti;
 //退出当前线程
 + (void)exit;
 //获取当前线程优先级
 + (double)threadPriority;
 // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0,1.0优先级最高
 + (BOOL)setThreadPriority:(double)p;

线程通信

1、在1个进程中,多个线程之间需要经常进行通信。
2、例如我们在子线程获取数据后,要回到主线程刷新界面。
3、1个线程传递数据给另1个线程。
4、在1个线程中执行完特定任务后,转到另1个线程继续执行任务。

回到主线程或者转到另一个线程的方法

// 返回主线程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 返回指定线程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

线程状态描述

1、创建线程对象后,线程处于新建New的状态。
2、调用start 开启线程后,会将线程加入到可调度线程池中,此时进入就绪Runnable状态。
3、当CPU调度到当前线程的时候,这时就会处于运行Running的状态。
4、当CPU调度其他线程的时候,当前线程又回到就绪状态。
5、当我们对当前线程调用了sleep或者等待同步锁的时候,此时又会进入线程阻塞Blocked状态。
   加入到可调度线程池中。
6、当任务执行完毕或强制退出后,进入到死亡Dead状态,此时线程对象被释放。

借一张图表示一下:

Objective-C基础笔记整理(三)多线程篇_第1张图片

线程安全

1、问题:多条线程抢夺同一块资源,可能会导致数据错乱和数据安全的问题。
2、解决:互斥锁,加锁可以使线程同步(多条线程在同一条线上按顺序的执行任务)。
3、优点:有效防止因多线程抢夺资源造成的数据安全问题。
4、缺点:需要消耗大量的CPU资源。
    //self : 锁对象,必须全局唯一
    @synchronized (self) {
        //代码片段,需要做的操作
    }


2.3、GCD


GCD优点

  • 自动管理线程生命周期(创建线程–>调度任务–>销毁线程)。
  • 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。

核心概念:任务和队列

  • 任务:执行什么操作。
  • 队列:存放任务。

    1、将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。
    2、任务的取出遵循队列的FIFO原则:先进先出,后进后出。
    

任务

(1)、同步任务:只能在当前线程执行任务,不具备开启新线程的能力.

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

(2)、异步任务:在新的线程中执行任务,具备开启线程的能力.

dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

队列

(1)、并发队列:可以让多个任务同时执行,并发功能只有在异步函数下才有效.

    //1.创建并发队列
    /**
     第一个参数:c语言字符串,标签
     第二个参数:队列的类型
     DISPATCH_QUEUE_CONCURRENT  并发
     DISPATCH_QUEUE_SERIAL      串行
     */
    dispatch_queue_t creatCurrrentQueue = dispatch_queue_create("creatCurrrentQueue", DISPATCH_QUEUE_CONCURRENT);


    //2.获取全局的并发队列
    /**
     第一个参数:优先级  选择默认的优先级
     第二个参数:留给以后用的  暂时传0
     */
    dispatch_queue_t getGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

(2)、串行队列:让任务一个接一个执行.

    //1.创建串行队列
    dispatch_queue_t creatQueue = dispatch_queue_create("creatQueue", DISPATCH_QUEUE_SERIAL);

    //2.获取全局的串行队列主队列 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
    dispatch_queue_t getQueue = dispatch_get_main_queue();

常用组合方式

    // 异步函数 + 获取全局并发队列 --- 例如获取数据
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 异步函数 + 获取全局串行队列(主队列)--- 例如 回到主线程 刷新UI界面
        dispatch_async(dispatch_get_main_queue(), ^{

        });

    });

同步函数 + 主队列:死锁

     //1.获得主队列
    dispatch_queue_t queue =  dispatch_get_main_queue();
    //2.同步函数
    dispatch_sync(queue, ^{
        NSLog(@"---download1---%@",[NSThread currentThread]);
    });
1、给主线程中添加任务,同步函数要求立刻马上执行任务,而主队列安排主线程来执行任务,
   但当前主线程在等待方法执行完毕, 因此就会相互等待而发生死锁。
2、如果此方法在子线程中调用,则不会形成死锁。

其它GCD使用方式

(1)、延迟执行

    /*
     第一个参数:延迟时间
     第二个参数:要执行的代码
     如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0)
     */
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
        //执行内容
    });

(2)、只执行一次,例如单例

        //整个程序运行过程中只会执行一次
        //onceToken用来记录该部分的代码是否被执行过
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"-----");
        });

(3)、队列组

    dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 执行1个耗时的异步操作

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的异步操作都执行完毕后,回到主线程...

});

(4)、快速迭代:开启多条线程,并发执行,相比于for循环在耗时操作中极大的提高效率和速度

        /*
     第一个参数:迭代的次数
     第二个参数:在哪个队列中执行
     第三个参数:block要执行的任务
     */
    dispatch_apply(10, queue, ^(size_t index) {
    });


2.4、NSOperation


NSOperation是苹果对GCD的封装,完全面向对象,不用考虑线程的生命周期、同步、加锁等问题。

NSOperation和NSOperationQueue分别对应GCD的任务和队列。

NSOperation和NSOperationQueue实现多线程的具体步骤:

1、将需要执行的操作封装到一个NSOperation对象中。
2、将NSOperation对象添加到NSOperationQueue中。
3、系统会自动将NSOperationQueue中的NSOperation取出来,将其封装的操作放到一条新线程中执行。

NSOperation

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类,它的子类有三种:

(1)、NSInvocationOperation

    /*
     第一个参数:目标对象
     第二个参数:选择器,要调用的方法
     第三个参数:方法要传递的参数
     */
    NSInvocationOperation *op  = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    //启动操作
    [op start];

(2)、NSBlockOperation(常用)

    //1.封装操作
    NSBlockOperation *blockOperation= [NSBlockOperation blockOperationWithBlock:^{
        //要执行的操作,在主线程中执行
    }];

    //2.追加操作,追加的操作在子线程中执行,可以追加多条操作
    [blockOperation addExecutionBlock:^{
        //追加的操作
    }];
    //启动操作
    [blockOperation start];

(3)、自定义子类继承NSOperation,实现内部相应的方法

    -(void)main
    {
    // 要执行的操作
    }

NSOperationQueue

队列:
(1)、主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
(2)、非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
(3)、NSOperation可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

添加操作到NSOperationQueue中

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

NSOperation和NSOperationQueue结合使用创建多线程

    // 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列
    NSOperationQueue *queue =[[NSOperationQueue alloc]init];
    // 2. 封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        //操作
    }];
    // 3. 将封装操作加入主队列
    // 也可以不获取封装操作对象 直接添加操作到队列中
    //[queue addOperationWithBlock:^{
    // 操作
    //}];
    [queue addOperation:op1];

NSOperation和NSOperationQueue的重要属性和方法

NSOperation的依赖

// 操作one依赖two,即one必须等two执行完毕之后才会执行
[one addDependency:two];

// 移除依赖
[one removeDependency:two];

NSOperation操作监听

op1.completionBlock = ^{
        NSLog(@"op1已经完成了---%@",[NSThread currentThread]);
    };

NSOperation线程之间的通信方法

// 回到主线程刷新UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
      //刷新操作
}];

你可能感兴趣的:(Objective-C基础笔记)