iOS-多线程学习笔记

262 阅读7分钟

多线程

iOS中的常见多线程方案

技术方案简介语言线程生命周期使用频率
pthread一套通用的多线程API
适用于Unix\Linux\Windows等系统
跨平台\可移植
使用难度大
C程序员管理几乎不用
NSThread使用更加面向对象
简单易用,可直接操作线程对象
OC程序员管理偶尔使用
GCD替代NSThread等线程技术C自动管理经常使用
NSOperation基于GCD(底层是GCD)OC自动管理经常使用

GCD的常用函数

GCD中有2个用来执行任务的函数

//创建异步任务
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

dispatch_async(queue, ^{
    NSLog(@"执行任务-%@", [NSThread currentThread]);
});

//创建同步任务
dispatch_sync(queue, ^{
    NSLog(@"执行任务-%@", [NSThread currentThread]);
});

//打印
//执行任务-<_NSMainThread: 0x60000077c040>{number = 1, name = main}
//执行任务-<_NSMainThread: 0x60000077c040>{number = 6, name = main}

GCD的队列

GCD的队列可以分为2大类型:

并发队列:

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)

并发功能只有在异步(dispatch_async)函数下才有效

串行队列:

让任务一个接着一个地执行

容易混淆的术语

有4个术语比较容易混淆:同步、异步、并发、串行

同步异步主要影响: 能不能开启新的线程

同步:在当前线程中执行任务,不具备开启新线程的能力

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

并发串行主要影响:任务的执行方式

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

各种队列的执行效果

并发队列手动串行队列主队列
同步(sync)没有开启新的线程
串行执行任务
没有开启新的线程
串行执行任务
没有开启新的线程
串行执行任务
异步(async)有开启新线程
并发执行任务
有开启新线程
串行执行任务
没有开启新的线程
串行执行任务

死锁问题

//会产生死锁
//因为下面的是全局同步任务
//在执行同步任务之前,是需要去结束上一个任务
//而上一个任务是viewDidLoad。是还没有结束的,所以这个任务也不会去执行,导致了死锁。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"2");
    });
    
    NSLog(@"3");
}
//是否会死锁
-(void)interview03{
    NSLog(@"1");
    
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueu", DISPATCH_QUEUE_SERIAL);
    //异步
    dispatch_async(queue, ^{
        NSLog(@"2");
        
        //同步 会死锁
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        
        NSLog(@"4");
    });
    
    NSLog(@"5");
}
//是否会死锁 不会
-(void)interview04{
    NSLog(@"1");
    
    dispatch_queue_t queue = dispatch_queue_create("myQueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"2");
        
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        
        NSLog(@"4");
    });
    
    NSLog(@"5");
}

死锁规律:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)。也就是说,sync任务所在的线程如果是串行就会导致。

在子线程中定时器不启动面试题

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0.0];
        NSLog(@"3");
    });
}

-(void)test{
    NSLog(@"2");
}

全局并发队列,子线程中执行一个几秒后的操作,打印结果:

2022-06-24 10:02:42.306969+0800 多线程[43027:644829] 1
2022-06-24 10:02:42.307178+0800 多线程[43027:644829] 3

2没有被打印。

原因是子线程不会主动去添加RunLoop,所以定时器的任务不会被执行。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:0.0];
        NSLog(@"3");
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}

-(void)test{
    NSLog(@"2");
}

添加RunLoop后,2被打印。结果是:

2022-06-24 10:08:42.851329+0800 多线程[43309:652496] 1
2022-06-24 10:08:42.851805+0800 多线程[43309:652496] 3
2022-06-24 10:08:42.852029+0800 多线程[43309:652496] 2

如果不添加延迟

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil];
        NSLog(@"3");
    });
}

-(void)test{
    NSLog(@"2");
}

立即执行,打印结果为:

2022-06-24 10:10:33.652839+0800 多线程[43389:655004] 1
2022-06-24 10:10:33.653042+0800 多线程[43389:655004] 2
2022-06-24 10:10:33.653197+0800 多线程[43389:655004] 3

并发队列

使用并发队列。先异步执行2个任务,完成后在异步执行一个任务。

// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);

// 添加异步任务
dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务1-%@", [NSThread currentThread]);
    }
});

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务2-%@", [NSThread currentThread]);
    }
});

// 等前面的任务执行完毕后,会自动执行这个任务
dispatch_group_notify(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务3-%@", [NSThread currentThread]);
    }
});
//打印结果
2022-06-24 11:53:22.587432+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587431+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587619+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587621+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587688+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587712+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587760+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587792+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587825+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587855+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588034+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588334+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588408+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588468+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588529+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}

回到主线程执行:

// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);

// 添加异步任务
dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务1-%@", [NSThread currentThread]);
    }
});

dispatch_group_async(group, queue, ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务2-%@", [NSThread currentThread]);
    }
});

// 回主线程执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    for (int i = 0; i < 5; i++) {
        NSLog(@"任务3-%@", [NSThread currentThread]);
    }
});

多线程的安全隐患

资源共享:

1块资源可能会被多个线程共享、也就是多个线程可能会访问同一块资源

比如多个线程访问同一个对象、同一个变量、同一个文件。

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

多线程安全隐患的解决方案

解决方案:使用线程同步技术(同步、就是协同步调,按预定的先后次序进行)

常见的线程同步技术是: 加锁

iOS中的线程同步方案

OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized

OSSpinLock

  • OSSpinLock 叫做 ”自旋锁“,等待锁的线程会处于忙等状态,一直占用CPU资源。

  • 目前已经不再安全,可能会出现优先级反转问题。

  • 如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。

  • 需要导入头文件 #import <libkern/OSAtomic.h>

//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁
bool result = OSSpinLockTry(&lock)
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);

os_unfair_lock

  • os_unfair_lock 用于取代不安全的OSSpinLock, 从iOS10开始才支持

  • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

  • 需要导入头文件 #import <os/lock.h>

//初始化
os_unfair_lock moneyLock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);

pthread_mutex

  • mutex叫做”互斥锁“,等待锁的线程会处于休眠状态

  • 需要导入头文件 #import <pthread.h>

//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
//有不同模式
//PTHREAD_MUTEX_DEFAULT 默认
//PTHREAD_MUTEX_RECURSIVE 递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
//初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);

//加锁
pthread_mutex_lock(&mutex);
//解说
pthread_mutex_unlock(&mutex);
//销毁锁
pthread_mutex_destroy(&mutex);

pthread_mutex – 条件 生产消费模式

-(instancetype)init{
    if (self = [super init]) {
        //初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        //递归锁
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        //初始化锁
        pthread_mutex_init(&_mutex, &attr);
        //销毁属性
        pthread_mutexattr_destroy(&attr);
        
        self.data = [NSMutableArray array];
        
      	pthread_cond_init(&_condition, NULL);
    }
    return self;
}

-(void)otherTest{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 删除数组中的元素
-(void)__remove{
    pthread_mutex_lock(&_mutex);
    
    if (self.data.count == 0) {
      	// 等待 解锁
      	// 向下执行 并 加锁
        pthread_cond_wait(&_condition, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");

    pthread_mutex_unlock(&_mutex);
    NSLog(@"2");
}

// 往数组中添加元素
-(void)__add{
    pthread_mutex_lock(&_mutex);

    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
  	// 通知等待的线程
    pthread_cond_signal(&_condition);
    
  	//广播 解开所有等待的锁
    pthread_cond_broadcast(&_condition);
  
    pthread_mutex_unlock(&_mutex);
    NSLog(@"1");
}

- (void)dealloc{
    pthread_mutex_destroy(&_mutex);
}

NSLock

NSLockpthread_mutex 的封装,看实现用到了 pthread_mutex 的代码。

@interface NSLockDemo()

@property (strong, nonatomic) NSLock *moneyLock;

@property (strong, nonatomic) NSLock *ticketLock;

@end

@implementation NSLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        
        self.moneyLock = [[NSLock alloc] init];
        
        self.ticketLock = [[NSLock alloc] init];
    
    }
    return self;
}

- (void)__drawMoney
{
    [self.moneyLock lock];
    
    [super __drawMoney];
    
    [self.moneyLock unlock];
}

NSCondition

// NSCondition是对mutex和cond的封装
// 而且遵守了NSLocking协议 
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

@interface NSConditionDemo ()

@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

-(instancetype)init{
    if (self = [super init]) {
        
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
        
    }
    return self;
}

-(void)otherTest{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 删除数组中的元素
-(void)__remove{
    [self.condition lock];
    
    if (self.data.count == 0) {
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");

    [self.condition unlock];
    
    NSLog(@"2");
}

// 往数组中添加元素
-(void)__add{
    [self.condition lock];

    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    [self.condition signal];
    //广播
    // [self.condition broadcast];
    
    [self.condition unlock];
    NSLog(@"1");
}