多线程操作共享资源的问题
- 共享资源
- 资源 : 一个全局的对象、一个全局的变量、一个文件.
- 共享 : 可以被多个对象访问.
- 共享资源 :可以被多个对象访问的资源.比如全局的对象,变量,文件.
- 在多线程的环境下,共享的资源可能会被多个线程共享,也就是多个线程可能会操作同一块资源.
- 当多个线程操作同一块资源时,很容易引发数据错乱和数据安全问题,数据有可能丢失,有可能增加,有可能错乱.
经典案例 : 火车站卖票,商品抢购
线程安全:同一块资源,被多个线程同时读写操作时,任然能够得到正确的结果,称之为线程是安全的.
卖票逻辑
代码实现卖票逻辑
- 先定义共享资源
@interface ViewController ()
/// 总票数(共享的资源)
@property (nonatomic,assign) int tickets;
@end
2.初始化余票数共享资源
- (void)viewDidLoad {
[super viewDidLoad];
// 设置余票数
self.tickets = 10;
}
3.卖票逻辑实现
- (void)saleTickets
{
// while 循环保证每个窗口都可以单独把所有的票卖完
while (YES) {
// 模拟网络延迟
[NSThread sleepForTimeInterval:1.0];
// 判断是否有票
if (self.tickets>0) {
// 有票就卖
self.tickets--;
// 卖完一张票就提示用户余票数
NSLog(@"剩余票数 => %zd %@",self.tickets,[NSThread currentThread]);
} else {
// 没有就提示用户
NSLog(@"没票了");
// 此处要结束循环,不然会死循环
break;
}
}
}
4.单线程
先确保单线程中运行正常
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 在主线程中卖票
[self saleTickets];
}
5.多线程
如果单线程运行正常,就修改代码,实现多线程环境
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 在主线程中卖票
// [self saleTickets];
// 售票口 A
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
thread1.name = @"售票口 A";
[thread1 start];
// 售票口 B
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
thread2.name = @"售票口 B";
[thread2 start];
}
资源抢夺结果
出错原因分析
解决多线程操作共享资源的问题
使用互斥锁/同步锁.
添加互斥锁
- (void)saleTickets
{
// while 循环保证每个窗口都可以单独把所有的票卖完
while (YES) {
// // 模拟休眠网络延迟
[NSThread sleepForTimeInterval:1.0];
// 添加互斥锁
@synchronized(self) {
// 判断是否有票
if (self.tickets>0) {
// 有票就卖
self.tickets--;
// 卖完一张票就提示用户余票数
NSLog(@"剩余票数 => %zd",self.tickets);
} else {
// 没有就提示用户
NSLog(@"没票了");
// 此处要结束循环,不然会死循环
break;
}
}
}
}
互斥锁小结
- 互斥锁,就是使用了线程同步技术.
- 同步锁/互斥锁:可以保证被锁定的代码,同一时间,只能有一个线程可以操作.
- self :锁对象,任何继承自NSObject的对像都可以是锁对象,因为内部都有一把锁,而且默认是开着的.
- 锁对象 : 一定要是全局的锁对象,要保证所有的线程都能够访问,self是最方便使用的锁对象.
- 互斥锁锁定的范围应该尽量小,但是一定要锁住资源的读写部分.
- 加锁后程序执行的效率比不加锁的时候要低.因为线程要等待解锁.
- 牺牲了性能保证了安全性.