美文网首页
iOS多线程-安全篇

iOS多线程-安全篇

作者: Jey | 来源:发表于2018-12-05 10:50 被阅读11次

多个线程访问同一块资源的时候,很容易引发数据混乱问题。
看下面的例子,3个线程分别买票7次,看下面打印结果

- (void)ticketTest{
    self.ticketsCount = 21;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            for (int i = 0; i < 7; i++) {
                [self sellingTickets];
            }
        });
    }
    
}
//卖票
- (void)sellingTickets{
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    
    NSLog(@"当前剩余票数-> %d", oldMoney);
}

输出:
2018-12-05 10:01:18.630619+0800 001--Block[7762:559583] 当前剩余票数-> 20
2018-12-05 10:01:18.630619+0800 001--Block[7762:559582] 当前剩余票数-> 20
2018-12-05 10:01:18.630660+0800 001--Block[7762:559581] 当前剩余票数-> 20
2018-12-05 10:01:18.630853+0800 001--Block[7762:559583] 当前剩余票数-> 19
2018-12-05 10:01:18.630853+0800 001--Block[7762:559582] 当前剩余票数-> 19
2018-12-05 10:01:18.631212+0800 001--Block[7762:559581] 当前剩余票数-> 18
2018-12-05 10:01:18.631220+0800 001--Block[7762:559583] 当前剩余票数-> 17
2018-12-05 10:01:18.631491+0800 001--Block[7762:559582] 当前剩余票数-> 16
2018-12-05 10:01:18.631606+0800 001--Block[7762:559581] 当前剩余票数-> 15
2018-12-05 10:01:18.632321+0800 001--Block[7762:559583] 当前剩余票数-> 14
2018-12-05 10:01:18.632695+0800 001--Block[7762:559582] 当前剩余票数-> 13
2018-12-05 10:01:18.633174+0800 001--Block[7762:559581] 当前剩余票数-> 12
2018-12-05 10:01:18.633746+0800 001--Block[7762:559583] 当前剩余票数-> 11
2018-12-05 10:01:18.634030+0800 001--Block[7762:559582] 当前剩余票数-> 10
2018-12-05 10:01:18.634240+0800 001--Block[7762:559581] 当前剩余票数-> 9
2018-12-05 10:01:18.635149+0800 001--Block[7762:559583] 当前剩余票数-> 8
2018-12-05 10:01:18.635715+0800 001--Block[7762:559582] 当前剩余票数-> 7
2018-12-05 10:01:18.636263+0800 001--Block[7762:559581] 当前剩余票数-> 6
2018-12-05 10:01:18.637100+0800 001--Block[7762:559583] 当前剩余票数-> 5
2018-12-05 10:01:18.637324+0800 001--Block[7762:559582] 当前剩余票数-> 4
2018-12-05 10:01:18.637685+0800 001--Block[7762:559581] 当前剩余票数-> 3

正常情况下我有21张票,然后卖了21次,剩余票数应该是0,但是打印结果竟然是3,所以这里就存在了线程安全问题。

出现线程安全的原因就是在同一个时间,多个线程同时读取一个值,像线程A和B同时读取了当前票数为10,等于是卖了两张票,但是总票数其实就减少了一张。

解决方法

线程同步方法,加锁。

两种锁的加锁原理

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

对比 互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

两种锁的应用

互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

1 临界区有IO操作
2 临界区代码复杂或者循环量大
3 临界区竞争非常激烈
4 单核处理器
至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

1、OSSpinLock自旋锁的使用

OSSpinLock叫做”自旋锁”,使用时需要导入头文件#import <libkern/OSAtomic.h>

//  OSSpinLock自旋锁的初始化
OSSpinLock _lock = OS_SPINLOCK_INIT;

//  锁定
OSSpinLockLock(&_lock);

// 解锁
OSSpinLockUnlock(&_lock);

这样我们就可以把上面买票代码加锁了

- (void)ticketTest{
    _ticketLock = OS_SPINLOCK_INIT;
    self.ticketsCount = 21;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (NSInteger i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            for (int i = 0; i < 7; i++) {
                [self sellingTickets];
            }
        });
    }
    
}
//卖票
- (void)sellingTickets{
    OSSpinLockLock(&_ticketLock);
    int oldMoney = self.ticketsCount;
    sleep(.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    OSSpinLockUnlock(&_ticketLock);
    NSLog(@"当前剩余票数-> %d", oldMoney);
}

这样就可以保证票数卖完,数量同步了
OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。而且还有一些安全性问题,具体参考不再安全的 OSSpinLock

2、os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等 需要导入头文件#import <os/lock.h>

//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
3、pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态。需要导入头文件#import <pthread.h> 使用步骤

4、NSLock

NSLock是对mutex普通锁的封装。pthread_mutex_init(mutex, NULL);

NSLock 遵循 NSLocking 协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO

#import "LockDemo.h"
@interface LockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@end
@implementation LockDemo
//卖票
- (void)sellingTickets{
[self.ticketLock lock];
[super sellingTickets];
[self.ticketLock unlock];
}

@end

相关文章

网友评论

      本文标题:iOS多线程-安全篇

      本文链接:https://www.haomeiwen.com/subject/dxhicqtx.html