iOS 开发 多线程详解之线程安全(资源共享)

多线程操作共享资源的问题

  • 共享资源
    1. 资源 : 一个全局的对象、一个全局的变量、一个文件.
    2. 共享 : 可以被多个对象访问.
    3. 共享资源 :可以被多个对象访问的资源.比如全局的对象,变量,文件.
  • 在多线程的环境下,共享的资源可能会被多个线程共享,也就是多个线程可能会操作同一块资源.
  • 当多个线程操作同一块资源时,很容易引发数据错乱和数据安全问题,数据有可能丢失,有可能增加,有可能错乱.
  • 经典案例 : 火车站卖票,商品抢购

  • 线程安全:同一块资源,被多个线程同时读写操作时,任然能够得到正确的结果,称之为线程是安全的.

卖票逻辑

iOS 开发 多线程详解之线程安全(资源共享)_第1张图片

代码实现卖票逻辑

  1. 先定义共享资源
@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 *)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];
}

资源抢夺结果

iOS 开发 多线程详解之线程安全(资源共享)_第2张图片

出错原因分析

iOS 开发 多线程详解之线程安全(资源共享)_第3张图片

解决多线程操作共享资源的问题

使用互斥锁/同步锁.

iOS 开发 多线程详解之线程安全(资源共享)_第4张图片

添加互斥锁

- (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是最方便使用的锁对象.
  • 互斥锁锁定的范围应该尽量小,但是一定要锁住资源的读写部分.
  • 加锁后程序执行的效率比不加锁的时候要低.因为线程要等待解锁.
  • 牺牲了性能保证了安全性.

你可能感兴趣的:(多线程,iOS开发)