概述
多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务。
多条线程同时访问同一块资源,比如操作同一个对象、统一变量、同一个文件,就会引发数据错乱和数据安全
的问题。
多线程引发问题实例
这里我也借助网上两个比较经典的案例,卖票和存取钱。
卖票案例
多个线程同时卖票,同一时间多个线层同时访问票的总数,就会引发数据错乱。
实例代码
@interface ViewController ()
@property (nonatomic, assign) int tickets;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets = 30;
[self saleTicketTest];
}
- (void)saleTicketWithName:(NSString *)name
{
int currTicketCount = self.tickets;
sleep(.2);
currTicketCount--;
self.tickets = currTicketCount;
NSLog(@"当前%@卖了一张,票还剩:%d张", name ,self.tickets);
}
- (void)saleTicketTest
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"A"];
}
});
dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"B"];
}
});
dispatch_async(queue, ^{
for (int i=0; i<10; i++) {
[self saleTicketWithName:@"C"];
}
});
}
@end
异常结果:
存钱取钱案例
实例代码
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, assign) int currMoney;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.currMoney = 1000;
[self moneyTest];
}
- (void)moneyTest
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self saveMoney];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self drawMoney];
}
});
}
// 存钱
- (void)saveMoney
{
int money = self.currMoney;
sleep(.3);
money += 100;
self.currMoney = money;
NSLog(@"存了100,还剩%d元", self.currMoney);
}
// 取钱
- (void)drawMoney
{
int money = self.currMoney;
sleep(.3);
money -= 50;
self.currMoney = money;
NSLog(@"取了50,还剩%d元", self.currMoney);
}
异常结果:
多线程安全隐患的解决方案
开发中解决多线程安全隐患的方案-使用线程同步技术。同步(协同步调,按预定的先后顺序执行)
常见的线程同步技术就是加锁
。
iOS中线程同步的方案根据性能优先级排序:
线程同步的方案 | 需要导入头文件 | 类别 | 注意事项 |
---|---|---|---|
os_unfair_lock | <os/lock.h> | 互斥锁 | iOS10才开始提供,用来替代OSSpinLock |
OSSpinLock | <libkern/OSAtomic.h> | 自旋锁 | 目前不在安全,可能会出现优先级反转问题 |
dispatch_semaphore | #import <pthread.h> | ||
pthread_mutex | #import <pthread.h> | ||
dispatch_queue(DISPATCH_SENTINEL) | #import <pthread.h> | ||
NSLock | |||
oNSCondition | #import <os/lock.h> | ||
pthread_mutex(recuresive) | #import <pthread.h> | ||
NSRecursiveLock | |||
NSConditionLock | |||
@synchronized |
OSSpinLock
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
需要导入头文件#import <libkern/OSAtomic.h>
os_unfair_lock
os_unfair_lock
用于取代不安全的OSSpinLock ,从iOS10开始才支持,从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等。
需要导入头文件#import <os/lock.h>
pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import <pthread.h>
注意:pthread_mutex在不使用时需要释放锁与属性。
在上面存钱取钱实例通过pthread_mutex实现线程安全。
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) int currMoney;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_mutexattr_t attr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化属性
// pthread_mutexattr_t att;
pthread_mutexattr_init(&_attr);
// 设置锁的类型
pthread_mutexattr_settype(& _attr, PTHREAD_MUTEX_DEFAULT);
// 初始化lock
pthread_mutex_init(&_lock, & _attr);
self.currMoney = 1000;
[self moneyTest];
}
- (void)dealloc
{
pthread_mutex_destroy(& _lock);
pthread_mutexattr_destroy(&_attr);
}
- (void)moneyTest
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self saveMoney];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i=0; i<4; i++) {
[self drawMoney];
}
});
}
// 存钱
- (void)saveMoney
{
pthread_mutex_lock(& _lock);
int money = self.currMoney;
sleep(.3);
money += 100;
self.currMoney = money;
NSLog(@"存了100,还剩%d元", self.currMoney);
pthread_mutex_unlock(& _lock);
}
// 取钱
- (void)drawMoney
{
pthread_mutex_lock(& _lock);
int money = self.currMoney;
sleep(.3);
money -= 50;
self.currMoney = money;
NSLog(@"取了50,还剩%d元", self.currMoney);
pthread_mutex_unlock(& _lock);
}
pthread_mutex – 递归锁
递归锁的特点:允许同一个线程对同一把锁进行重复加锁,这里强调的是同一个线程。
递归锁在是使用场合是什么呢?
方法嵌套
- (void)test1
{
pthread_mutex_lock(&_lock);
NSLog(@"任务1");
[self test2];
pthread_mutex_unlock(&_lock);
}
- (void)test2
{
pthread_mutex_lock(&_lock);
NSLog(@"任务2");
pthread_mutex_unlock(&_lock);
}
递归
- (void)test1
{
pthread_mutex_lock(&_lock);
NSLog(@"任务1");
[self test1];
pthread_mutex_unlock(&_lock);
}
pthread_mutex – 条件锁
多线程任务执行是没有顺序的,比如现在想实现一个消费者模式的多线程案例
当有生产者产出数据是才需要让消费者获取数据执行逻辑。由于生产与消费是多线程,可以在消费逻辑中加入判断,当没有数据时让消费线程休眠释放锁,生产者线程获取锁执行生产,当生产者有数据时发送信号通知消费者线程,消费者线程唤醒并重新再次加锁。
这是就需要用pthread_mutex条件锁。
实例代码
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;
@property (nonatomic, strong) NSMutableArray *array;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.array = [NSMutableArray array];
// 传入NUll 使用PTHREAD_MUTEX_DEFAULT类型的锁
pthread_mutex_init(&_lock, NULL);
pthread_cond_init(&_condition, NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self remove];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self add];
});
}
// 删除数组中的元素
- (void)remove
{
pthread_mutex_lock(&_lock);
if (self.array.count == 0) {
// 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
pthread_cond_wait(&_condition, &_lock);
}
NSLog(@"删除了元素");
[self.array removeLastObject];
pthread_mutex_unlock(&_lock);
}
// 往数组添加元素
- (void)add
{
pthread_mutex_lock(&_lock);
[self.array addObject:@"test"];
// 发送信号 激活等待该条件的线程
pthread_cond_signal(&_condition);
// 广播 激活所有等待该条件的线程
// pthread_cond_broadcast(&_condition);
NSLog(@"添加了元素");
pthread_mutex_unlock(&_lock);
}
NSLock NSRecursiveLock
NSLock是对mutex普通锁的封装。遵守了
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
NSCondition
NSCondition是对mutex和cond的封装。
实例代码
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) NSCondition *condition;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.array = [NSMutableArray array];
self.condition = [[NSCondition alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self remove];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self add];
});
}
// 删除数组中的元素
- (void)remove
{
// pthread_mutex_lock(&_lock);
[self.condition lock];
if (self.array.count == 0) {
// 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
[self.condition wait];
}
NSLog(@"删除了元素");
[self.array removeLastObject];
[self.condition unlock];
// pthread_mutex_unlock(&_lock);
}
// 往数组添加元素
- (void)add
{
// pthread_mutex_lock(&_lock);
[self.condition lock];
[self.array addObject:@"test"];
// 发送信号 激活等待该条件的线程
[self.condition signal];
// 广播 激活所有等待该条件的线程
[self.condition broadcast];
NSLog(@"添加了元素");
[self.condition unlock];
// pthread_mutex_unlock(&_lock);
}
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值。可以让子线程按着指定的顺序执行。
示例代码:
dispatch_semaphore 信号量
semaphore叫做”信号量”。信号量的初始值,可以用来控制线程并发访问的最大数量。信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步。
dispatch_queue串行队列
直接使用GCD的串行队列,也是可以实现线程同步的
@synchronized
@synchronized是对mutex递归锁的封装,@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作