iOS 多线程

283 阅读6分钟

一、基础概念

1.进程

  • 是一个具有独立功能的程序,操作系统分配资源的基本单元
  • 好比如说,我们打开一个iOS的APP,他在操作系统上运行之后,这就成为系统上的一个进程了(在其他平台可以是多进程的)
  • 进程之间是独立的,互不影响的

2.线程

  • 是用来执行任务的最小单位
  • 线程是进程中的一个实体,线程组成进程

线程与进程的关系:线程是进程的执行单位,进程里的线程可以进行资源共享

3.任务

  • 线程里的那段要执行的代码。
  • 执行方式:
    1. 同步执行(Sync) —— 不具备开线程的能力,会一直等待前面的任务完成后才会执行,会阻塞线程
    2. 异步执行(Async) —— 具备开启线程的能力(不一定就能开,看队列),无需等待就能继续执行当前任务下面的其他任务,不阻塞线程

4.队列

  • 队列是一种常见的数据结构,特殊的线性表。特点为:先进先出(FIFO原则)。
  • 作用:解决一些任务执行和等待的公平性,先进者先服务。
  • 分为串行队列和并发队列,两者都符合 FIFO(先进先出)的原则。

4.1.串行队列:

  • 任务一个接一个的来执行,必须等前一个任务完成,下一个任务才开始执行。不管有多少条线程进入到这种队列,都必须排队开始。

4.2.并发队列:

  • 同时允许多个任务并发执行。多线程的优势可以在里面重发显现,效率高。

综合关系:任务通过线程来执行,线程是任务的载体,任务在队列中排序等待执行,任务的执行由队列开启线程来完成。而这一切都是包含在进程里,进程的顺利运行必须依靠大家一起完成。

下面展示一个进程下同步队列的简单示例图:

二、iOS中常见的多线程方案

1.开启线程的方式

  1. pthread(c) — 可以跨平台,下面三种都是基于pthread
  2. NSThread(OC) — 使线程变得可操作,有线程对象,线程的生命周期可以手动管理
  3. GCD(C) — 可以充分地利用设备的多核,生命周期自动管理
  4. NSOperation(OC) — 是对GCD的封装,使用上更面向对象,生命周期自动管理

2.多线程机制:

2.1.同步异步函数 + 串行并行队列 (两两组合,四种情况)

问题:使用了 异步函数 就一定能开启多线程了吗?

答:不是!

原因:在iOS里面能开启线程的只有两种情况:

         1. 异步+并发队列
         2.异步+手动创建的串行队列

特别说明:主队列是特殊的串行队列,不管和什么函数配合都不能开启多线程

2.2.死锁

  • 死锁:任务之间相互等待,造成线程阻塞
  • 情况:同步函数 + 串行队列 (两个条件同时存在,就会造成死锁)

2.3.线程安全

线程之间是可以共享资源的,但是这会造成由于资源抢夺而导致的一些线程不安全问题。

为了解决线程安全的问题,我们需要对共享的资源进行加锁,让线程有序地访问。

2.3.1.常用锁的种类

锁的作用:保证资源的完整性,你访问,我就不能访问
  1. 互斥锁:
    • 简单的保证共享资源的完整性,确保我操作,你就不能操作
    • @synchronized
    • NSLock
  2. 自旋锁:
    • 和互斥锁很像,不同点在于资源被占用时,他不会休息,而是一直轮循去询问资源是否可用
    • OSSpinLock:
      • 可以达到效果,但是存在优先级反转的问题。就是如果等待的线程优先级比较高,那么会一直占用CPU(相当于执行一个while循环),导致原来持有资源的低等级的线程无法释放锁,从而一直卡死
    • os_unfair_lock:解决优先级反转的问题
  3. 递归锁:
    • 防止一条线程里多次加锁从而造成死锁的情况。场景:递归
    • NSRecursiveLock
  4. 信号量:
    • 可以控制线程的并发数量
    • dispatch_semaphore_t
  5. 读写锁:
    • 把写和读分隔开,如我们常用的栅栏函数就是读写锁
    • dispatch_barrier_async / dispatch_barrier_sync
  6. 条件锁:
    • 线程检测器根据条件决定是否要执行
    • NSCondition
    • NSConditionLock

2.3.2.控制线程的执行顺序

  • 解决场景:A、B、C三个任务执行完,再执行D
2.3.2.1. 信号量

1.1.创建信号

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

1.2.等待信号: 让信号量 -1

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
...需要执行的任务

1.3.释放信号:让信息量 +1(只有当上面semaphore的值大于或等于0时,之前在等待的线程才会一条条的执行)

...任务执行完成,释放信号
dispatch_semaphore_signal(semaphore);
2.3.2.2. 栅栏函数
注意:
1. 栅栏函数对于自己创建的并发队列才有意义,不能使用系统的全局并发队列,因为这样会把一些系统的操作给卡住
2. dispatch_barrier_sync: 会把代码写在这函数下面的所有操作都给卡住,知道他执行完,不管是否在同一队列!
3. dispatch_barrier_async: 只会把同一队列里往下的操作给卡住,写在这函数下面的其他队列的操作不受影响
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"开始任务A");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"任务A finish.");
});
dispatch_async(queue, ^{
    NSLog(@"开始任务B");
    [NSThread sleepForTimeInterval:0.5];
    NSLog(@"任务B finish.");
});
dispatch_async(queue, ^{
    NSLog(@"开始任务C");
    [NSThread sleepForTimeInterval:0.2];
    NSLog(@"任务C finish.");
});

dispatch_barrier_async(queue, ^{
    NSLog(@"============ barrier ===========");
});

dispatch_async(queue, ^{
    NSLog(@"开始任务D");
    [NSThread sleepForTimeInterval:0.2];
    NSLog(@"任务D finish.");
});
2.3.2.3.dispatch_group
  1. 创建group:dispatch_group_create()
  2. Enter group:dispatch_group_enter(group)
  3. Leave group:dispatch_group_leave(group)
  4. 所有任务完成之后,回调:dispatch_group_notify()
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"开始任务A");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"任务A finish.");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"开始任务B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"任务B finish.");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"开始任务C");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"任务C finish.");
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"开始任务D");
});
2.3.2.4. NSBlockOperation依赖
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
//创建操作
NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操作,线程:%@",[NSThread currentThread]);
    }];
NSBlockOperation *operationB=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第2次操作,线程:%@",[NSThread currentThread]);
    }];
NSBlockOperation *operationC=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第3次操作,线程:%@",[NSThread currentThread]);
    }];
NSBlockOperation *operationD=[NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第4次操作,线程:%@",[NSThread currentThread]);
    }];
//添加依赖
[operationA addDependency:operationB];
[operationB addDependency:operationC];
[operationC addDependency:operationD];
//将操作添加到队列中去
[queue addOperation:operationA];
[queue addOperation:operationB];
[queue addOperation:operationC];
[queue addOperation:operationD];
注意:
1. 如果对ABC的任务执行顺序也要有顺序,那么只能使用信号量、栅栏函数、NSOperationQueue
2. 其中栅栏函数每个任务开始都使用栅栏dispatch_barrier_async,不使用dispatch_async即可保证按序进行