多线程

作者: Abner_XuanYuan | 来源:发表于2023-03-05 23:45 被阅读0次

1、基本定义

进程:一个运行中的程序(APP)是一个进程。进程是系统分配资源的基本单位,每个进程都是独立的,每个进程均运行在其专用且受保护的内存空间内。是操作系统结构的基础,主要用于管理资源。
线程:调度的基本单位,是进程的基本组成单元。所有任务都是在线程中执行,每个进程至少有一个线程(主线程)。
进程和线程的关系:一个进程至少有一个线程(主线程)。
主线程:每个进程都会有且仅有一个主线程。主线程用来更新 UI,所有 UI 的更新都要在主线程上执行。因此耗时操作不要放在主线程。
多线程:一个进程中可以开启多条线程,多条线程可以同时执行不同的任务。多线程同时执行其实是 CPU 在多条线程之间快速切换。
多线程的优缺点
优点:能适当提高程序的执行效率;能适当提高资源的利用率。
缺点:创建更多的线程会增加开销、降低程序的性能;线程越多 CPU 在线程间切换的开销越大;程序设计会更加复杂。

2、线程的状态

线程状态有:新建、就绪、执行、阻塞、死亡。
新建:创建新的线程。
就绪:线程执行条件满足时,会向该线程发送 start 消息,线程被加入可调度线程池等待 CPU 调度。
执行:CPU 去线程池调度线程。一个线程在完成/死亡之前在非阻塞状态下,可以在就绪和执行之间切换并且线程的就绪和执行状态的切换是由 CPU 决定的,程序员不能干预。
阻塞:执行条件不足线程进入等待阻塞状态。
死亡:正常死亡,线程运行完毕。非正常死亡,线程内部终止。

3、多线程方案

多线程的实现包括:pthread、NSThread、GCD、NSOperation。


多线程方案对比
1、pthread

pthread 是一套通用 C 语言的多线程的 API,可以在Unix / Linux / Windows 等系统跨平台使用,使用 C 语言编写,需要程序员自己管理线程的生命周期。

2、NSThread

NSThread 是苹果官方提供的,是一套 OC 语言的 API。使用起来比 pthread 更加面向对象,简单易用,可以直接操作线程对象。不过也需要需要程序员自己管理线程的生命周期(主要是创建)。

3、GCD

是一套 C 语言的 API,旨在替代 NSThread 技术,简单易用。可以充分利用设备的多核,自动管理线程生命周期。

4、NSOperation

基于GCD(底层是GCD)的 OC 语言的封装,使用更加面向对象,可以自动管理线程生命周期。

4、GCD 详解及使用

1、核心概念 -- 任务和队列

任务:要做什么事情(即放在 block 中的代码)。分为同步执行和异步执行。
同步(sync):只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block 中的任务执行完毕 dispatch 函数才会返回,然后当前线程才会继续往下运行。

dispatch_sync(queue, ^{
    //code
});

异步(async):可以在新的线程中执行任务,具备开启线程的能力,但不一定会开启新的线程,dispatch 函数会立即返回, 然后 Block 在后台异步执行,即当前线程会直接往下执行,不会阻塞当前线程。

dispatch_async(queue, ^{
    //code
});

注:异步执行具有开辟新线程的能力,但不一定开辟新线程。

队列:用来存放任务,是一种特殊的线性表。分为串行队列和并发队列。
串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。

参数一 :  队列的唯一标识符,用于 DEBUG,可为空。
参数二 :  串行队列/并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

dispatch_queue_t queue = dispatch_queue_create("chaunXingDuiLie", DISPATCH_QUEUE_SERIAL);

并发队列:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)。
注:并发队列只有在异步(dispatch_async)的时候才有效果。

参数一 :  队列的唯一标识符,用于 DEBUG,可为空。
参数二 :  串行队列/并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

dispatch_queue_t queue = dispatch_queue_create("bingFaDuiLie", DISPATCH_QUEUE_CONCURRENT);

两种特殊队列:主队列和全局并发队列。
主队列:可以通过 dispatch_get_main_queue() 获取。主队列的任务都会放在主线程执行。其实质是一个普通的串行队列。
全局并发队列:可以通过 dispatch_get_global_queue() 获取。

参数一:队列优先级,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT
参数二:(不知道什么用,直接传 0)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

注:全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2  // 高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)优先级(自定义队列的优先级都是默认优先级)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低优先级
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台优先级
2、使用流程

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

3、任务和队列组合
任务和队列

同步主队列:死锁。主队列中追加的同步任务和主线程本身的任务相互等待,阻塞主队列执行,造成主队列所在的主线程死锁。
同步和异步决定会不会开启新线程。同步不会开启新线程,异步会开启新线程。

同步主队列会出现死锁,但同步非主队列中不会造成死锁?

同步主队列示例
同步主队列原理
同步非主队列示例
同步非主队列原理

同步并发 示例

同步并发
解析:
1、同步方式提交任务,无论提交到串行队列还是并发队列,最终都是在当前线程执行。所以 1 是在主线程执行并输出。
2、由于是同步方式提交,所以 block 是在主线程执行。2 会输出。
3、内部的 block 是一个并发队列,不影响外部 block 执行,故会依次输出 3 和 4。
4、最后输出 5 。

异步并发 示例

异步并发
解析:
1、performSelector: 方法调用所在的线程必须有 runloop ,否则该方法不会被调用。
2、示例中异步全局队列分配完成之后,block 会在 GCD 底层所维护的线程池当中的某一个线程中执行,而该线程默认所没有开启 runloop。

通过栅栏函数(dispatch_barrier_async)实现一个 “多读单写” 示例

多读单写
4、线程间通信
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

     //全局队列任务
    dispatch_async(queue, ^{
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
        });
    });
}
5、高级用法

栅栏函数:dispatch_barrier_async
原理:在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });

    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
    });

    dispatch_async(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_async(queue, ^{
        // 追加任务 4
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
    });
}

延时执行方法:dispatch_after

- (void)after {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}

一次性代码:dispatch_once
使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,即使在多线程的环境下,dispatch_once 也可以保证线程安全。

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行 1 次的代码(这里面默认是线程安全的)
    });
}

快速迭代方法:dispatch_apply
dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素逐个遍历。dispatch_apply 可以在多个线程中同时(异步)遍历多个数字。无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕。

/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

队列组:dispatch_group
异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
使用步骤:

  1. 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合来实现 dispatch_group_async。
  2. 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。

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

- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    dispatch_group_t group =  dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

        NSLog(@"group---end");
    });
}

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

- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    dispatch_group_t group =  dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });

    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"group---end");

}

dispatch_group_enter、dispatch_group_leave

  1. dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
  2. dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
  3. 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

        NSLog(@"group---end");
    });
}

信号量:dispatch_semaphore (保持线程同步将异步执行任务转换为同步执行任务.保证线程安全为线程加锁)

  1. dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  2. dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  3. dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
/**
 * semaphore 线程同步
 */
- (void)semaphoreSync {

    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

        number = 100;

        dispatch_semaphore_signal(semaphore);
    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}
/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");

    semaphoreLock = dispatch_semaphore_create(1);

    self.ticketSurplusCount = 50;

    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);

    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });

    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);

        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");

            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }

        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

关闭GCD任务 dispatch_block_cancel

  1. 必须是由 dispatch_block_create 创建 dispatch_block_t,才可以用 dispatch_block_cancel 关闭.
  2. 未执行的可以用 dispatch_block_cancel 方法 cancel 掉,若已经执行则 cancel 不掉.关闭正在运行的使用 dispatch_block_testcancel 方法.

5、NSOperation 详解及应用

1、核心 -- NSOperation(操作)和 NSOperationQueue(操作队列)

操作:要做的事情。
操作队列:用来存放操作的队列。有主队列和自定义队列。主队列运行在主线程上。自定义队列在后台运行。

2、NSOperation、NSOperationQueue 作用
  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便的控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
3、使用步骤

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

4、NSOperation 使用

由于 NSOperation 是一个抽象类,不能直接用来封装操作,所以可用其子类,NSInvocationOperation、NSBlockOperation、自定义继承自 NSOperation 的子类。

NSInvocationOperation

- (void)useInvocationOperation {

    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];
}

/**
 * 任务1
 */
- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}

注:
默认情况下,调用 start 方法后并不会开启新线程去执行操作,而是在当先线程中同步执行操作。只有将 NSOperation 放在 NSOperationQueue 中,才会异步执行操作。

NSBlockOperation

- (void)useBlockOperation {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.调用 start 方法开始执行操作
    [op start];
}

NSBlockOperation 可以通过 addExecutionBlock: 添加额外的操作,这些操作(包括 blockOperationWithBlock: 中的操作)可以在不同线程中执行,只有所有的操作完成后,才视为完成。

/**
 * 使用子类 NSBlockOperation
 * 调用方法 AddExecutionBlock:
 */
- (void)useBlockOperationAddExecutionBlock {

    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 2.添加额外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.调用 start 方法开始执行操作
    [op start];
}

注:
使用 NSBlockOperation 并调用方法 AddExecutionBlock: 的情况下,blockOperationWithBlock: 方法中的操作和 addExecutionBlock: 中的操作是在不同的线程中异步执行的。
如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

自定义继承自 NSOperation 的子类

// YSCOperation.h 文件
#import <Foundation/Foundation.h>

@interface YSCOperation : NSOperation

@end

// YSCOperation.m 文件
#import "YSCOperation.h"

@implementation YSCOperation

- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }
}

@end
/**
 * 使用自定义继承自 NSOperation 的子类
 */
- (void)useCustomOperation {
    // 1.创建 YSCOperation 对象
    YSCOperation *op = [[YSCOperation alloc] init];
    // 2.调用 start 方法开始执行操作
    [op start];
}

在当前调用的线程中执行
5、队列

NSOperationQueue:分为主队列和自定义队列。自定义队列包含串行和并发功能。
主队列:凡是添加到主队列中的操作,都会放在主线程中执行。

// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定义队列:又叫非主队列。添加到这种队列的操作,会自动放在子线程中执行。同时包含串行,并发功能。

// 自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
6、将操作添加到队列中

addOperation: 先创建好操作,再将创建好的操作添加到队列中

/**
 * 使用 addOperation: 将操作加入到操作队列中
 */
- (void)addOperationToQueue {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}

addOperationWithBlock: 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

/**
 * 使用 addOperationWithBlock: 将操作加入到操作队列中
 */

- (void)addOperationWithBlockToQueue {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到队列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}
7、maxConcurrentOperationCount

NSOperationQueue 通过 maxConcurrentOperationCount 设置串行执行和并发执行。
maxConcurrentOperationCount 最大并发操作数:可以用来控制一个队列中可以有多少个操作同时参加并发执行。
maxConcurrentOperationCount 控制的不是并发线程的数量而是一个队列中同时能并发执行的最大操作数,而且一个操作也并非只能在一个线程中执行。
maxConcurrentOperationCount 默认情况下为 -1 表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列,只能串行执行。
maxConcurrentOperationCount 大于1时为并发队列,操作并发执行。需要说明的是,最大并发数有个最大值,不会无限大的。

/**
 * 设置 MaxConcurrentOperationCount(最大并发操作数)
 */
- (void)setMaxConcurrentOperationCount {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = 8; // 并发队列

    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}
8、NSOperation 操作依赖

控制和查看操作之间的执行顺序。
添加操作依赖:- (void)addDependency:(NSOperation *)op;
移除操作依赖:- (void)removeDependency:(NSOperation *)op;

/**
 * 操作依赖
 * 使用方法:addDependency:
 */
- (void)addDependency {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.添加依赖
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

    // 4.添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}
9、NSOperation 优先级

queuePriority:可以改变同一队列中操作的优先级,不能用来改变不同队列中操作的优先级。不设置的时候,默认操作的优先级是 NSOperationQueuePriorityNormal 。

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

注意:
优先级不能取代依赖关系;
队列中先执行高优先级的操作,再执行低优先级的操作,同优先级按顺序执行;
准备就绪的操作比未准备就绪的操作先执行,无论两者谁的优先级高。

10、线程间通信
/**
 * 线程间通信
 */
- (void)communication {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步进行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }

        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 进行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
    }];
}
11、线程同步和线程安全
/**
 * 线程安全:使用 NSLock 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */

- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    self.ticketSurplusCount = 50;

    self.lock = [[NSLock alloc] init];  // 初始化 NSLock 对象

    // 1.创建 queue1,queue1 代表北京火车票售卖窗口
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;

    // 2.创建 queue2,queue2 代表上海火车票售卖窗口
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;

    // 3.创建卖票操作 op1
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];

    // 4.创建卖票操作 op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketSafe];
    }];

    // 5.添加操作,开始卖票
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {

        // 加锁
        [self.lock lock];

        if (self.ticketSurplusCount > 0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }

        // 解锁
        [self.lock unlock];

        if (self.ticketSurplusCount <= 0) {
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}
12、常用属性和方法

NSOperation 常用属性和方法

1. 取消操作方法
- (void)cancel; 可取消操作,实质是标记 isCancelled 状态。

2. 判断操作状态方法
- (BOOL)isFinished; 判断操作是否已经结束。
- (BOOL)isCancelled; 判断操作是否已经标记为取消。
- (BOOL)isExecuting; 判断操作是否正在在运行。
- (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

3. 操作同步
- (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。
- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperationQueue 常用属性和方法

1. 取消/暂停/恢复操作
- (void)cancelAllOperations; 可以取消队列的所有操作。
- (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
- (void)setSuspended:(BOOL)b;  可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。

2. 操作同步
- (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。

3. 添加/获取操作
- (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
- (NSUInteger)operationCount; 当前队列中的操作数。

4. 获取队列
+ (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
+ (id)mainQueue; 获取主队列。

注:

  1. 这里的取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
  2. 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

6、线程安全

线程安全方案--线程锁

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/fuxaqftx.html