您的位置:首页 > 移动开发 > IOS开发

iOS 多线程安全问题以及各种锁使用

2019-03-06 11:53 260 查看

多线程安全问题

多线程使用不当会出现资源竞争,比如多个线程同时对一块资源进行修改,就会出现数据错乱的情况。

@property (nonatomic, assign) int ticketsCount;

- (void)ticketTest {

self.ticketsCount = 15;

dispatch_queue_t quene = dispatch_get_global_queue(0, 0);

dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
[self saleticket];
}
});

dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
[self saleticket];
}
});

dispatch_async(quene, ^{
for (int i = 0; i < 5; i++) {
[self saleticket];
}
});

}

- (void)saleticket {

int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}

调用

ticketTest
方法
打印结果:

还剩14张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)}
还剩14张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)}
还剩14张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}
还剩13张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)}
还剩13张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)}
还剩12张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}
还剩11张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)}
还剩10张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)}
还剩9张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)}
还剩9张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}
还剩8张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)}
还剩6张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}
还剩7张票 - <NSThread: 0x600002a09280>{number = 5, name = (null)}
还剩5张票 - <NSThread: 0x600002a06d00>{number = 3, name = (null)}
还剩4张票 - <NSThread: 0x600002a06d80>{number = 4, name = (null)}

总票数为15,虽然子线程调用

saleticket
方法15次,但是打印的数据并没有递减为0。

线程同步技术

常见的线程同步技术是加锁

  • OSSpinLock
    自旋锁
    导入头文件
    #import <libkern/OSAtomic.h>

    - (void)saleticket {
    // 初始化
    static OSSpinLock lock = OS_SPINLOCK_INIT;
    // 加锁
    OSSpinLockLock(&lock);
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    // 解锁
    OSSpinLockUnlock(&lock);
    }

    调用

    ticketTest
    方法
    打印结果:

    还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}

    自旋锁等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。
    并且现在已经不安全,可能出现优先级反转的问题。
    如果等待锁的优先级较高,它会一直占用着CPU的资源,优先级低的线程就无法释放锁。
    在iOS10被苹果废弃。

  • os_unfair_lock

    导入头文件
    #import <os/lock.h>

    @property (assign, nonatomic) os_unfair_lock lock;
    
    - (instancetype)init {
    if (self = [super init]) {
    // 初始化
    self.lock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
    }
    
    - (void)saleticket {
    
    // 加锁
    os_unfair_lock_lock(&lock);
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    // 解锁
    os_unfair_lock_unlock(&lock);
    }

    调用

    ticketTest
    方法
    打印结果:

    还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}

    os_unfair_lock
    用于取代不安全的
    OSSpinLock

    等待
    os_unfair_lock
    锁的线程处于休眠状态,不是忙等状态。
    在iOS10开始使用。

  • pthread_mutex
    互斥锁

    普通用法

    导入头文件

    #import <pthread.h>

    @property (assign, nonatomic) pthread_mutex_t mutex;
    
    - (void)__initMutex:(pthread_mutex_t *)mutex {
    //    // 初始化属性
    //    pthread_mutexattr_t attr;
    //    pthread_mutexattr_init(&attr);
    //    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    //    // 初始化锁
    //    pthread_mutex_init(mutex, &attr);
    //    // 销毁属性
    //    pthread_mutexattr_destroy(&attr);
    
    // 初始化锁 NULL等同于PTHREAD_MUTEX_DEFAULT
    pthread_mutex_init(mutex, NULL);
    }
    
    - (instancetype)init {
    if (self = [super init]) {
    // 初始化
    [self __initMutex:&_mutex];
    }
    return self;
    }
    
    - (void)saleticket {
    
    // 加锁
    pthread_mutex_lock(&_mutex);
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    // 解锁
    pthread_mutex_unlock(&_mutex);
    }

    调用

    ticketTest
    方法
    打印结果:

    还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}

    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

    上面使用的是Normal类型同

    OSSpinLock
    os_unfair_lock
    作用一样

    递归用法
    递归锁在被同一线程重复获取时不会产生死锁
    递归类型

    PTHREAD_MUTEX_RECURSIVE

    - (void)__initMutex:(pthread_mutex_t *)mutex
    {
    // 递归锁:允许同一个线程对一把锁进行重复加锁
    
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    }
    
    - (instancetype)init
    {
    if (self = [super init]) {
    [self __initMutex:&_mutex];
    }
    return self;
    }
    
    /**
    线程1:otherTest(+-)
    otherTest(+-)
    otherTest(+-)
    
    线程2:otherTest(等待)
    */
    
    - (void)otherTest
    {
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 10) {
    count++;
    [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
    }
    
    - (void)dealloc
    {
    pthread_mutex_destroy(&_mutex);
    }

    条件用法

    @property (assign, nonatomic) pthread_mutex_t mutex;
    @property (assign, nonatomic) pthread_cond_t cond;
    @property (strong, nonatomic) NSMutableArray *data;
    
    - (instancetype)init
    {
    if (self = [super init]) {
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(&_mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    
    // 初始化条件
    pthread_cond_init(&_cond, NULL);
    
    self.data = [NSMutableArray array];
    }
    return self;
    }
    
    - (void)otherTest
    {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
    }
    
    // 线程1
    // 删除数组中的元素
    - (void)__remove
    {
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
    // 等待 先解锁进入等待状态
    // 接收到条件信号后,先加锁执行下面的代码,在解锁
    pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);
    }
    
    // 线程2
    // 往数组中添加元素
    - (void)__add
    {
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号
    pthread_cond_signal(&_cond);
    // 广播
    //    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
    }
    
    - (void)dealloc
    {
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
    }
  • NSLock、NSRecursiveLock

    NSLock
    是对
    mutex
    普通锁的封装
    NSRecursiveLock
    是对
    mutex
    递归锁的封装

    @property (strong, nonatomic) NSLock *lock;
    
    - (instancetype)init {
    if (self = [super init]) {
    // 初始化
    self.lock = [[NSLock alloc] init];
    }
    return self;
    }
    
    - (void)saleticket {
    
    // 加锁
    [self.lock lock];
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    // 解锁
    [self.lock unlock];
    }

    调用

    ticketTest
    方法
    打印结果:

    还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}

    NSRecursiveLock
    用法与
    NSLock
    的用法一样。

  • NSCondition

    NSCondition
    是对mutex和cond的封装

    @property (strong, nonatomic) NSCondition *condition;
    @property (strong, nonatomic) NSMutableArray *data;
    
    - (instancetype)init
    {
    if (self = [super init]) {
    self.condition = [[NSCondition alloc] init];
    self.data = [NSMutableArray array];
    }
    return self;
    }
    
    - (void)otherTest
    {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
    }
    
    // 生产者-消费者模式
    
    // 线程1
    // 删除数组中的元素
    - (void)__remove
    {
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
    // 等待
    [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];
    }
    
    // 线程2
    // 往数组中添加元素
    - (void)__add
    {
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号
    [self.condition signal];
    
    // 广播
    //    [self.condition broadcast];
    [self.condition unlock];
    
    }
  • NSConditionLock

    它是对
    NSCondition
    的进一步封装,可以设置具体的条件值

    @property (strong, nonatomic) NSConditionLock *conditionLock;
    
    - (instancetype)init
    {
    if (self = [super init]) {
    // 初始化的时候 设置条件为1
    self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
    }
    
    - (void)otherTest
    {
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
    }
    
    - (void)__one
    {
    // 加锁
    [self.conditionLock lock];
    
    NSLog(@"__one");
    sleep(1);
    // 解锁时设置锁的条件为2
    [self.conditionLock unlockWithCondition:2];
    }
    
    - (void)__two
    {
    // 当条件为2时 加锁
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    // 解锁时设置锁的条件为3
    [self.conditionLock unlockWithCondition:3];
    }
    
    - (void)__three
    {
    // 当条件为3时 加锁
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    // 解锁
    [self.conditionLock unlock];
    }
  • dispatch_queue

    直接使用GCD的串行队列,也可以实现线程同步

    @property (strong, nonatomic) dispatch_queue_t queue;
    
    - (instancetype)init {
    if (self = [super init]) {
    // 初始化
    self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
    }
    
    - (void)saleticket {
    
    dispatch_sync(self.moneyQueue, ^{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    });
    }

    调用

    ticketTest
    方法
    打印结果:

    还剩14张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩13张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩12张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩11张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩10张票 - <NSThread: 0x6000003114c0>{number = 3, name = (null)}
    还剩9张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩8张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩7张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩6张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩5张票 - <NSThread: 0x600000310cc0>{number = 4, name = (null)}
    还剩4张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩3张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩2张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩1张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
    还剩0张票 - <NSThread: 0x600000311580>{number = 5, name = (null)}
  • dispatch_semaphore

    信号量的初始值可以控制线程并发访问的最大值
    初始值为1时,代表同时只允许1条线程访问资源,保证线程的同步

    @property (strong, nonatomic) dispatch_semaphore_t semaphore;
    @property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
    
    - (instancetype)init {
    if (self = [super init]) {
    // 初始化
    self.semaphore = dispatch_semaphore_create(5);
    self.ticketSemaphore = dispatch_semaphore_create(1);
    }
    return self;
    }
    
    - (void)saleticket {
    // 让信号量的值-1 为0 则等待,相当于加锁
    dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    // 让信号量的值+1,相当于解锁
    dispatch_semaphore_signal(self.ticketSemaphore);
    }
    
    - (void)otherTest
    {
    for (int i = 0; i < 20; i++) {
    [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
    }
    
    // 线程的最大并发数为 5
    - (void)test
    {
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
    }

    调用otherTest会发现每隔两秒打印5条信息。

  • @synchronized

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

    - (void)saleticket {
    @synchronized(self) { // objc_sync_enter
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    } // objc_sync_exit
    }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐