多线程的安全隐患-(小码哥底层原理)

在使用多线程技术的时候,往往会遇到多个线程访问同一块资源,导致资源数据错乱,如下例:

  • 例1
-(void)saleTicket
{
    int oldTicketCount = self.ticktCount;
    sleep(.2);
    oldTicketCount -- ;
    self.ticktCount = oldTicketCount;
    NSLog(@"还剩%d张票-%@",oldTicketCount,[NSThread currentThread]);
}
-(void)ticketTest
{
    self.ticktCount = 15;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
}

打印结果:

2020-12-01 12:40:49.649515+0800 多线程的安全隐患1[21018:196122] 还剩14张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649521+0800 多线程的安全隐患1[21018:196123] 还剩14张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649533+0800 多线程的安全隐患1[21018:196129] 还剩14张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649664+0800 多线程的安全隐患1[21018:196123] 还剩12张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649664+0800 多线程的安全隐患1[21018:196129] 还剩12张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649666+0800 多线程的安全隐患1[21018:196122] 还剩13张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649741+0800 多线程的安全隐患1[21018:196123] 还剩11张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649754+0800 多线程的安全隐患1[21018:196129] 还剩10张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.649778+0800 多线程的安全隐患1[21018:196122] 还剩9张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.649810+0800 多线程的安全隐患1[21018:196123] 还剩8张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.649946+0800 多线程的安全隐患1[21018:196129] 还剩7张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.650073+0800 多线程的安全隐患1[21018:196123] 还剩6张票-<NSThread: 0x600002a99700>{number = 5, name = (null)}
2020-12-01 12:40:49.650184+0800 多线程的安全隐患1[21018:196122] 还剩5张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
2020-12-01 12:40:49.650297+0800 多线程的安全隐患1[21018:196129] 还剩4张票-<NSThread: 0x600002a954c0>{number = 6, name = (null)}
2020-12-01 12:40:49.650413+0800 多线程的安全隐患1[21018:196122] 还剩3张票-<NSThread: 0x600002ad5fc0>{number = 4, name = (null)}
  • 例2
#pragma mark - 存钱取钱
//取钱
-(void)drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    NSLog(@"取出20,还剩%d-%@",oldMoney,[NSThread currentThread]);
}
//存钱
-(void)saveMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    NSLog(@"存入50,还剩%d-%@",oldMoney,[NSThread currentThread]);
}
-(void)moneyTest
{
    self.money = 100;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];
        }
    });
    
}

打印结果

2020-12-01 13:05:04.321877+0800 多线程的安全隐患1[21392:215326] 取出20,还剩80-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.321882+0800 多线程的安全隐患1[21392:215328] 存入50,还剩150-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.321989+0800 多线程的安全隐患1[21392:215326] 取出20,还剩130-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.321994+0800 多线程的安全隐患1[21392:215328] 存入50,还剩180-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322061+0800 多线程的安全隐患1[21392:215326] 取出20,还剩160-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322085+0800 多线程的安全隐患1[21392:215328] 存入50,还剩210-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322137+0800 多线程的安全隐患1[21392:215326] 取出20,还剩190-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322145+0800 多线程的安全隐患1[21392:215328] 存入50,还剩240-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322278+0800 多线程的安全隐患1[21392:215326] 取出20,还剩220-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322467+0800 多线程的安全隐患1[21392:215328] 存入50,还剩270-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322580+0800 多线程的安全隐患1[21392:215326] 取出20,还剩250-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322774+0800 多线程的安全隐患1[21392:215328] 存入50,还剩300-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.322887+0800 多线程的安全隐患1[21392:215326] 取出20,还剩280-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.322996+0800 多线程的安全隐患1[21392:215328] 存入50,还剩330-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.323105+0800 多线程的安全隐患1[21392:215326] 取出20,还剩310-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.457707+0800 多线程的安全隐患1[21392:215328] 存入50,还剩360-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.457741+0800 多线程的安全隐患1[21392:215326] 取出20,还剩340-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.457880+0800 多线程的安全隐患1[21392:215328] 存入50,还剩390-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}
2020-12-01 13:05:04.457941+0800 多线程的安全隐患1[21392:215326] 取出20,还剩370-<NSThread: 0x600003cd1c40>{number = 6, name = (null)}
2020-12-01 13:05:04.458034+0800 多线程的安全隐患1[21392:215328] 存入50,还剩420-<NSThread: 0x600003cdf1c0>{number = 7, name = (null)}

造成结果错乱的原因主要由下图进行解释,多个线程同时对同一块资源进行操作。


image.png

解决方案:线程同步(协同步调,按照预定的先后顺序)技术
常用的线程同步技术:锁🔒

iOS中的线程同步方案:(性能从高到低排序)

  • os_unfair_lock
  • OSSpinkLock 自旋锁(已过期)
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized
OSSpinkLock-(自旋锁)等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
  • 目前已经不再安全,可能会出现优先级反转问题
  • 如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
  • 需要导入头文件#import <libkern/OSAtomic.h>

优先级反转:如线程thread1优先级较高,线程thread2优先级较低,如果出现线程thread2先加了锁,那thread1的状态就是未解锁,处于忙等状态,但是由于thread1的优先级较高,CPU可能会分配大量资源给thread1,就会导致优先级低的thread2不能解锁。现在已经弃用了OSSpinkLock。

 //初始化锁
    self.lock = OS_SPINLOCK_INIT;//需要使用同一把锁,所以用属性保存这把锁
//加锁
    OSSpinLockLock(&_lock);
//解锁
    OSSpinLockUnlock(&_lock);
//尝试加锁
    BOOL result = OSSpinLockTry(&_lock);
  • 例1和例2中加锁OSSpinkLock
#pragma mark - 存钱取钱
//取钱
-(void)drawMoney
{
    //加锁
    OSSpinLockLock(&_moneyLock);
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    NSLog(@"取出20,还剩%d-%@",oldMoney,[NSThread currentThread]);
    //解锁
    OSSpinLockUnlock(&_moneyLock);
}
//存钱
-(void)saveMoney
{
    //加锁
    OSSpinLockLock(&_moneyLock);
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    NSLog(@"存入50,还剩%d-%@",oldMoney,[NSThread currentThread]);
    //解锁
    OSSpinLockUnlock(&_moneyLock);
}
-(void)moneyTest
{
    self.money = 100;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];
        }
    });
    
}
#pragma mark - 卖票
-(void) saleTicket
{
    //加锁
    OSSpinLockLock(&_ticketLock);
    int oldTicketCount = self.ticktCount;
    sleep(.2);
    oldTicketCount -- ;
    self.ticktCount = oldTicketCount;
    NSLog(@"还剩%d张票-%@",oldTicketCount,[NSThread currentThread]);
    //解锁
    OSSpinLockUnlock(&_ticketLock);
}
-(void)ticketTest
{
    self.ticktCount = 15;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self sellTicket];
        }
    });
}
os_unfair_lock
  • 从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态
  • 使用的时候需要导入头文件#import <os/lock.h>
//初始化锁
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
 //加锁
    os_unfair_lock_lock(&_ticketLock);
//解锁
    os_unfair_lock_unlock(&_ticketLock);
pthread_mutex
/*
 * Mutex type attributes
 */
#define PTHREAD_MUTEX_NORMAL        0
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2   //递归锁
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL

/*
  • mutex叫做“互斥锁”,等待锁的过程会处于休眠状态
  • 需要导入头文件#import <pthread.h>
    //静态初始化
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    //初始化锁的属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
    
    //初始化锁
    pthread_mutex_init(&lock, &attr);
    //加锁
    pthread_mutex_lock(&_ticketLock);
    //解锁
    pthread_mutex_unlock(&_ticketLock);
    //销毁相关资源
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_destroy(&lock);
pthread_mutex递归锁
    //递归锁:允许同一个线程对同一个锁重复加锁
    //初始化锁的属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    //递归锁:
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
    //初始化锁
    pthread_mutex_init(&lock, &attr);
    //销毁属性
    pthread_mutexattr_destroy(&attr);
   //初始化条件
    pthread_cond_init(&_cond, NULL);
    //唤醒等待_cond条件的线程
    pthread_cond_signal(&_cond);//信号 
   //激活所有等待这个条件的线程
    pthread_cond_broadcast(&_cond);//广播
pthread_mutex的条件使用
  • 写一个通过多线程操锁添加数组元素和删除数组元素的方法(删除元素的时候必须保证数组中是有元素的)代码如下:
#import "MutexDemo3.h"
#import <pthread.h>
@interface MutexDemo3()

@property (nonatomic,assign)pthread_mutex_t mutex;
@property (nonatomic,strong)NSMutableArray *data;
@property (nonatomic,assign)pthread_cond_t cond;
@end
@implementation MutexDemo3
- (instancetype)init
{
    self = [super init];
    if (self) {
        //静态初始化
//        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        self.mutex = [self __initMutexWith:_mutex];
    }
    return self;
}
-(pthread_mutex_t)__initMutexWith:(pthread_mutex_t)lock
{
    //递归锁:允许同一个线程对同一个锁重复加锁
    //初始化锁的属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    
    //初始化锁
    pthread_mutex_init(&lock, &attr);
    //销毁属性
    pthread_mutexattr_destroy(&attr);
    //初始化条件
    pthread_cond_init(&_cond, NULL);
    self.data = [NSMutableArray array];
    
    return lock;
}
//删除数组中的元素
-(void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    if (self.data.count == 0) {
        //等待条件(休眠并放开_mutex这把锁)
        //如果等待到条件,会继续先加锁_mutex
        pthread_cond_wait(&_cond,&_mutex);
    }
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    pthread_mutex_unlock(&_mutex);
}
//往数组中添加元素
-(void)__add
{
    pthread_mutex_lock(&_mutex);
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    //唤醒等待_cond条件的线程
    pthread_cond_signal(&_cond);//信号
//    pthread_cond_broadcast(&_cond);//广播(激活所有等待这个条件的线程)
    pthread_mutex_unlock(&_mutex);
}
-(void)otherTest
{
    [[[NSThread alloc]initWithTarget:self selector:@selector(__remove) object:nil]start];
    sleep(1.0);
    [[[NSThread alloc]initWithTarget:self selector:@selector(__add) object:nil]start];
}
- (void)dealloc
{
    //销毁相关资源
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}
@end
NSLock和NSRecursiveLock
  • NSLock是mutex普通锁的封装
  • NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;


@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

NSCondition
  • NSCondition是对mutex和cond的封装
  • 可用于生产者-消费者模式
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
NSConditionLock
  • NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

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

@end
  • 例:
- (instancetype)init
{
    self = [super init];
    if (self) {
        //若初始化的时候不设置条件值,默认为0
//        self.lock - [[NSConditionLock alloc]init];
        self.lock = [[NSConditionLock alloc]initWithCondition:1];
    }
    return self;
}
-(void)__one
{
    [self.lock lockWhenCondition:1];
    NSLog(@"__one");
    [self.lock unlockWithCondition:2];
}
-(void)__two
{
    [self.lock lockWhenCondition:2];
    NSLog(@"__two");
    [self.lock unlockWithCondition:3];
   
}
-(void)__three
{
    [self.lock lockWhenCondition:3];
    NSLog(@"__three");
    [self.lock unlock];
   
}
-(void)otherTest
{
    [[[NSThread alloc]initWithTarget:self selector:@selector(__one) object:nil]start];
    sleep(1.0);
    [[[NSThread alloc]initWithTarget:self selector:@selector(__two) object:nil]start];
    [[[NSThread alloc]initWithTarget:self selector:@selector(__three) object:nil]start];
}
dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • 直接使用GCD的串行队列,也是可以实现线程同步的

  • 例:

#import "SerialQueueDemo.h"
@interface SerialQueueDemo()
@property (nonatomic,strong)dispatch_queue_t ticketQueue;
@property (nonatomic,strong)dispatch_queue_t moneyQueue;
@end
@implementation SerialQueueDemo
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
        self.moneyQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
- (void)__saveMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __saveMoney];
    });
    
}
- (void)__drawMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __drawMoney];
    });
    
}
- (void)__saleTicket
{
    dispatch_sync(self.ticketQueue, ^{
        [super __saleTicket];
    });
}
@end
dispatch_semaphore
  • semaphore:信号量
  • 信号量的初始值,可以用来控制线程\color{red}{并发访问的最大数量}
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
       //信号量的初始值
        int value = 5;
        //初始化信号量
        self.semaphore = dispatch_semaphore_create(value);
         //如果信号量的值 >0,就让心好凉的值减1,然后继续往下执行
          //如果信号量的值是<=0,就会休眠等待,直到信号量的值>0,就让信号量的值减1,然后继续往下执行
          dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
          //让信号量的值+1
           dispatch_semaphore_signal(self.semaphore);
#import "SemaphoreDemo.h"
@interface SemaphoreDemo()

@property (nonatomic,strong)dispatch_semaphore_t semaphore;
@end
@implementation SemaphoreDemo
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.semaphore = dispatch_semaphore_create(5);
    }
    return self;
}
-(void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]start];
    }
}
-(void)test
{
    //如果信号量的值 >0,就让信号量的值减1,然后继续往下执行
    //如果信号量的值是<=0,就会休眠等待,直到信号量的值>0,就让信号量的值减1,然后继续往下执行
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    sleep(2);
    NSLog(@"test - %@",[NSThread currentThread]);
    dispatch_semaphore_signal(self.semaphore);
}
@end
  • 通过宏定义信号量锁
#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t oneceToken; \
dispatch_once(&oneceToken, ^{ \
    semaphore = dispatch_semaphore_create(1); \
});\
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);
@synchronized
  • @synchronized是对mutex递归锁的封装
  • 源码查看:objc4中的objc- sync.mm文件
  • @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁解锁操作。

源码:

  • 调用int objc_sync_enter(id obj):
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }

    return result;
}
  • SyncData
typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;
  • recursive_mutex_tt:
template <bool Debug>
class recursive_mutex_tt : nocopy_t {
    os_unfair_recursive_lock mLock;

  public:
    //初始化为递归锁
    constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
        lockdebug_remember_recursive_mutex(this);
    }

    constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
        : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
    { }
    //加锁
    void lock()
    {
        lockdebug_recursive_mutex_lock(this);
        os_unfair_recursive_lock_lock(&mLock);
    }
    //解锁
    void unlock()
    {
        lockdebug_recursive_mutex_unlock(this);

        os_unfair_recursive_lock_unlock(&mLock);
    }
  • 使用方法
#import "SynchronizedDemo.h"

@implementation SynchronizedDemo

- (void)__saveMoney
{
    @synchronized(self){
        [super __saveMoney];
    }
}
- (void)__drawMoney
{
    @synchronized(self){
        [super __drawMoney];
    }
}
- (void)__saleTicket
{
    static NSObject *lock;
    static dispatch_once_t oneceToken;
    dispatch_once(&oneceToken, ^{
        lock = [[NSObject alloc]init];
    });
    @synchronized(lock){
        [super __saleTicket];
    }
}
//递归
-(void)otherTest
{
    @synchronized ([self class]) {
        NSLog(@"123");
        [self otherTest];
    }
}
@end

产生死锁的四个必要条件:

  • \color{red}{互斥条件:}一个资源只能被一个进程占用
  • \color{red}{不可剥夺条件:}某个进程占用了资源,就只能他自己去释放。
  • \color{red}{请求和保持条件:}某个进程之前申请了资源,我还想再申请资源,之前的资源还是我占用着,别人别想动。除非我自己不想用了,释放掉。
  • \color{red}{循环等待条件:}一定会有一个环互相等待。

自旋锁和互斥锁

  • 通过跟踪OSSpinkLock(自旋锁)的汇编代码,可以看到如下_OSSpinLockLockSlow中,一直在进行循环,即线程等待解锁的过程


  • 通过跟踪pthread_mutex(互斥锁)可以看到在进入__psynch_mutexwait函数时,会调用syscall,这个时候再在控制台输入si(一步汇编一步汇编的执行)时,断点就会过掉---即线程休眠了。通过跟踪os_unfair_lock也是这样,所以可以猜测os_unfair_lock也是互斥锁。


    image.png
什么情况使用自旋锁比较好?
  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  • CPU资源不紧张
  • 多核处理器
什么情况使用互斥锁比较好?
  • 预计线程等待锁的时间较长
  • 单核处理器
  • 临界区有IO操作
  • 临界区代码复杂或者循环量大
  • 临界区竞争非常激烈

atomic

  • atomic用于保证属性setter和getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
  • 可以参考源码objc4的objc- accessors.mm
  • 它并不能保证使用属性的过程是线程安全的

一般在Mac上使用比较多,iOS设备使用比较少,因为比较消耗性能
源码:

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    //noatomic
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    //atomic
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {//nonatomic
        oldValue = *slot;
        *slot = newValue;
    } else {//atomic
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

iOS中的读写(IO)安全方案

多读单写

  • 同一时间,只能有1个线程进行写操作
  • 同一时间,允许多个线程进行读的操作
  • 同一时间,不允许既有写的操作,又有读的操作
    实现方案:
  • pthread_rwlock:读写锁
  • dispatch_barrier_async:异步栅栏调用
pthread_rwlock读写锁
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@property (nonatomic,assign)pthread_rwlock_t lock;
@end

@implementation ViewController
/**
 同一时间,只能有1个线程进行写操作
 同一时间,允许多个线程进行读的操作
 同一时间,不允许既有写的操作,又有读的操作
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化锁
   pthread_rwlock_init(&_lock, NULL);
    for (int i = 0; i < 50; i++) {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            [self read];
            
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}
//多读单写
//读操作应该可以多条线程同时读取
-(void)read
{
    pthread_rwlock_rdlock(&_lock);
    sleep(1);
    NSLog(@"read - begin,%@",[NSThread currentThread]);
    pthread_rwlock_unlock(&_lock);
    NSLog(@"read - end,%@",[NSThread currentThread]);
}

//同一时刻只能允许一条线程写入操作
-(void)write
{
    pthread_rwlock_wrlock(&_lock);
    sleep(1);
    NSLog(@"write - begin,%@",[NSThread currentThread]);
    pthread_rwlock_unlock(&_lock);
    NSLog(@"write - end,%@",[NSThread currentThread]);
}

打印结果截取:从结果中可以看到,有多条线程同时读的操作,但是没有同时写和同时读的操作


cong
dispatch_barrier_async
  • 这个函数传入的并发队列必须是自己通过dispatch_queue_create创建的
  • 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
//初始化队列
    self.queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
 //读
    dispatch_async(self.queue, ^{
};
 //写
    dispatch_barrier_async(self.queue, ^{
};
  • 例:
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic,strong) dispatch_queue_t queue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        [self read];
        [self read];
        [self write];   
    }
}
-(void)read
{
    dispatch_async(self.queue, ^{
        NSLog(@"read-begin-%@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"read-end-%@",[NSThread currentThread]);
    });
}
-(void)write
{
    dispatch_barrier_async(self.queue, ^{
        NSLog(@"write-begin-%@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"write-end-%@",[NSThread currentThread]);
    });
}
@end

参考链接:https://ke.qq.com/course/package/11609?quicklink=1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容