iOS底层-多线程(二)

iOS中的线程同步方案

  1. 将买卖票和存取钱封装到一个基类中

     #import <Foundation/Foundation.h>    
     @interface ZHBaseDemo : NSObject
     /**
      存取钱演示
      */
     -(void)moneyTest;
     /**
      存钱
      */
     -(void)saveMoney;
     /**
      取钱
      */
     -(void)drawMoney;
     /**
      卖一张票
      */
     -(void)saleTicket;
     /**
      卖多张票
      */
     -(void)saleTicketTest;
     @end
      
        
     #import "ZHBaseDemo.h"
    
     @interface ZHBaseDemo ()
     @property (nonatomic, assign) int ticketsCount;
     @property (nonatomic, assign) int money;
     @end
        
     @implementation ZHBaseDemo
        
     /**
      存取钱演示
      */
     -(void)moneyTest{
         self.money = 100;
         dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
         dispatch_async(queue, ^{
             for (int i =0; i<3; i++) {
                 [self saveMoney];
             }
         });
         dispatch_async(queue, ^{
             for (int i =0; i<3; i++) {
                 [self drawMoney];
             }
         });
     }
        
     /**
      存钱
      */
     -(void)saveMoney{
          
         int oldMoney = self.money;
         sleep(.2);
         oldMoney+= 50;
         self.money = oldMoney;
         NSLog(@"存50,还剩%d元====%@",oldMoney,[NSThread currentThread]);
           
     }
     /**
      取钱
      */
     -(void)drawMoney{
           
         int oldMoney = self.money;
         sleep(.2);
         oldMoney-= 20;
         self.money = oldMoney;
         NSLog(@"取20,还剩%d元====%@",oldMoney,[NSThread currentThread]); 
     }
        
     /**
      卖一张票
      */
     -(void)saleTicket{
         int oldTicketsCount = self.ticketsCount;
         sleep(.2);
         oldTicketsCount--;
         self.ticketsCount = oldTicketsCount;
         NSLog(@"还剩%d张票====%@",oldTicketsCount,[NSThread currentThread]);
     }
        
     /**
      卖多张票
      */
     -(void)saleTicketTest{
         self.ticketsCount = 15;
         dispatch_async(dispatch_get_global_queue(0, 0), ^{
             for (int i =0; i<2; i++) {
                 [self saleTicket];
             }
         });
         dispatch_async(dispatch_get_global_queue(0, 0), ^{
             for (int i =0; i<2; i++) {
                 [self saleTicket];
             }
         });
         dispatch_async(dispatch_get_global_queue(0, 0), ^{
             for (int i =0; i<2; i++) {
                 [self saleTicket];
             }
         });
     }
        
     @end
    

os_unfair_lock

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

     #import "ZHBaseDemo.h"
    
     @interface OSUnfairLockDemo : ZHBaseDemo
        
     @end
        
     #import "OSUnfairLockDemo.h"
     #import <os/lock.h>
        
     @interface OSUnfairLockDemo ()
     @property (nonatomic, assign) os_unfair_lock moneyLock;
     @property (nonatomic, assign) os_unfair_lock ticketLock1;
     @end
        
     @implementation OSUnfairLockDemo
        
     - (instancetype)init{
         self = [super init];
         if (self) {
             //初始化
             self.moneyLock = OS_UNFAIR_LOCK_INIT;
             self.ticketLock1 = OS_UNFAIR_LOCK_INIT;
         }
         return self;
     }
     -(void)saleTicket{
         //尝试加锁,已经加锁就不走,没有加锁就加锁
         if (os_unfair_lock_trylock(&_ticketLock1)) {
            [super saleTicket];
             //解锁
             os_unfair_lock_unlock(&_ticketLock1);
         }
     }
        
     -(void)drawMoney{
         //加锁
         os_unfair_lock_lock(&_moneyLock);
         [super drawMoney];
         //解锁
         os_unfair_lock_unlock(&_moneyLock);
     }
        
     -(void)saveMoney{
         //加锁
         os_unfair_lock_lock(&_moneyLock);
         [super saveMoney];
         //解锁
         os_unfair_lock_unlock(&_moneyLock);
     }
        
     @end
    

pthread_mutex

  1. 凡是以pthread开头的基本上都是跨平台使用的
  2. mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
  3. 需要导入头文件#import
  4. 基本用法示例

     #import "PthreadMutexLockDemo.h"
     #import <pthread.h>
     @interface PthreadMutexLockDemo ()
     @property (nonatomic, assign) pthread_mutex_t moneyLock;
     @property (nonatomic, assign) pthread_mutex_t ticketLock1;
     @property (nonatomic, assign) pthread_mutex_t mutexLock;
     @end
        
     /**
      //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
        
      */
     @implementation PthreadMutexLockDemo
        
     -(void)__initMutex:(pthread_mutex_t *)mutex{
         //初始化锁的属性
         pthread_mutexattr_t attr;
         pthread_mutexattr_init(&attr);
         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
         //初始化锁
         pthread_mutex_init(mutex, &attr);
            
         //销毁相关资源
     //    pthread_mutexattr_destroy(&attr);
     //    pthread_mutex_destroy(mutex);
     }
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             [self __initMutex:&_moneyLock];
             [self __initMutex:&_ticketLock1];
             [self __initMutex:&_mutexLock];
         }
         return self;
     }
     -(void)saleTicket{
         //尝试加锁,已经加锁就不走,没有加锁就加锁
         if (pthread_mutex_trylock(&_ticketLock1)) {
             [super saleTicket];
             //解锁
             pthread_mutex_unlock(&_ticketLock1);
         }
            
     }
        
     -(void)drawMoney{
         //加锁
         pthread_mutex_lock(&_moneyLock);
         [super drawMoney];
         //解锁
         pthread_mutex_unlock(&_moneyLock);
     }
        
     -(void)saveMoney{
         //加锁
         pthread_mutex_lock(&_moneyLock);
         [super saveMoney];
         //解锁
         pthread_mutex_unlock(&_moneyLock);
     }
        
     //这里需要销毁锁
     -(void)dealloc{
         pthread_mutex_destroy(&_ticketLock1);
         pthread_mutex_destroy(&_moneyLock);
     }
        
     @end
    
  5. pthread_mutex之递归锁
    1. 下面的死锁现象

       /*********死锁现象分析**********/
       -(void)otherTest{
           //加锁
           pthread_mutex_lock(&_mutexLock);
                  
           //只有_mutexLock的锁解开才能执行otherTest2中的代码
           [self otherTest2];
                  
           NSLog(@"==========%s",__func__);
                  
           //但是要解锁必须otherTest2执行完成,因此造成死锁
           //解锁
           pthread_mutex_unlock(&_mutexLock);
       }
              
       -(void)otherTest2{
           //进来发现_mutexLock已经锁住了,所以无法进行下去,等待
           //加锁
           pthread_mutex_lock(&_mutexLock);
                  
           NSLog(@"==========%s",__func__);
           //解锁
           pthread_mutex_unlock(&_mutexLock);
       }
      
      1. otherTest将_mutexLock锁住,内部调用otherTest2
      2. 但是otherTest2也是用的_mutexLock锁,发现锁住了,那就线程等待,不能继续执行,一直等到_mutexLock锁打开为止。
      3. otherTest的解锁在otherTest2执行完之后才能开锁,因此造成了死锁现象
      4. 解决办法
        1. 这2个方法用不同的锁即可
      5. 疑问,如果是递归呢? 怎么办呢

          -(void)otherTest{
             //加锁
             pthread_mutex_lock(&_mutexLock);
             static int count = 0;
             count ++;
             //只有_mutexLock的锁解开才能执行otherTest
             if(count >10) return;
             [self otherTest];
             //一次也不会执行
             NSLog(@"==========%s",__func__);
             //解锁
             pthread_mutex_unlock(&_mutexLock);
         }
        
    2. 递归锁

      1. 将__initMutex这个方法中的下面这个属性改成递归属性,就是一把递归锁

         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        
      2. 这样就可以解决上面的问题了。
      3. 那么问题来了,递归锁既然可以重复加锁,那么当多个线程访问的时候,那岂不是又不安全了?
        1. 递归锁的原理: 允许同一个线程对一把锁进行重复加锁
        2. 即当多个线程来访问时,不同的线程是不能重复加锁的,仍然是安全的
  6. pthread_mutex – 条件
    1. 代码举例:

       #import "PthreadMutexLockDemo2.h"
       #import <pthread.h>
              
       @interface PthreadMutexLockDemo2 ()
       @property (nonatomic, assign) pthread_mutex_t mutexLock;
       @property (nonatomic,strong) NSMutableArray *data;
       @property (nonatomic, assign) pthread_cond_t cond;
       @end
       @implementation PthreadMutexLockDemo2
              
       - (instancetype)init
       {
           self = [super init];
           if (self) {
               //初始化锁的属性
               pthread_mutexattr_t attr;
               pthread_mutexattr_init(&attr);
               //递归锁
               pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
               //初始化锁
               pthread_mutex_init(&_mutexLock, &attr);
                      
               self.data = [NSMutableArray array];
               //初始化条件
               //NULL代表使用默认属性
               pthread_cond_init(&_cond, NULL);
           }
           return self;
       }
       //删除数组中的元素
       -(void)__remove{
           //加锁
           pthread_mutex_lock(&_mutexLock);
           NSLog(@"------remove");
                  
           //一旦发现当前数组无元素就开始等待,让当前线程休眠。但是同时把锁打开,允许别的线程使用这把锁加锁
           if (self.data.count == 0) {
               //等待条件(进入休眠,放开_mutexLock锁;被唤醒后,会再次对_mutexLock锁住)
               pthread_cond_wait(&_cond, &_mutexLock);
           }
                  
           [self.data removeLastObject];
           NSLog(@"删除了元素");
           //解锁
           pthread_mutex_unlock(&_mutexLock);
       }
       //往数组中添加元素
       -(void)__add{
           //加锁
           pthread_mutex_lock(&_mutexLock);
           NSLog(@"------add");
           [self.data addObject:@"test"];
           NSLog(@"添加了元素");
           //激活一个等待该条件的线程
           //发信号通知等待(_cond),不用等了,唤醒等待的线程,让它继续执行
           pthread_cond_signal(&_cond);
           //激活所有等待该条件的线程
       //    pthread_cond_broadcast(&_cond);
           //解锁
           pthread_mutex_unlock(&_mutexLock);
       }
       -(void)otherTest{
           [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
           sleep(1);
           [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
       }
       //这里需要销毁锁
       -(void)dealloc{
           pthread_mutex_destroy(&_mutexLock);
           pthread_cond_destroy(&_cond);
       }
              
       @end
      
      1. 分析流程
        1. 线程1先进入__remove,但是发现data没有数据
        2. 让线程1等待,进入休眠状态,不继续执行,同时放开_mutexLock
        3. 此时线程2进入__add_mutexLock上锁(因为线程1已经给他放开了),执行addobject
        4. 执行完成,唤醒线程1(此时线程2还没有开锁,需要等着线程2开完锁),线程2_mutexLock解锁,线程1_mutexLock加锁,继续执行删除remove
        5. 线程1执行remove完毕,_mutexLock开锁
      2. 测试

         -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
             PthreadMutexLockDemo2 *demo3 = [[PthreadMutexLockDemo2 alloc] init];
             [demo3 otherTest];
         }
        
      3. 打印:

         ------remove
         ------add
         添加了元素
         删除了元素
        
        1. 可以看到尽管先执行了remove方法,但是最终停下了了
        2. 去执行了add,执行完,才返回执行删除
    2. 使用场合:

      1. 某个线程执行完之后当前线程再执行

NSLock、NSRecursiveLock

  1. NSLock是对mutex普通锁的封装

     @protocol NSLocking
     //加锁
     - (void)lock;
     //解锁
     - (void)unlock;
     @end
        
     @interface NSLock : NSObject <NSLocking> {
     @private
         void *_priv;
     }
     //试着加锁(不等待)
     - (BOOL)tryLock;
     //等待limit时间,时间内锁打开就加锁,到limit时间了,还没有开锁那就不加了,返回加锁失败
     - (BOOL)lockBeforeDate:(NSDate *)limit;
     @property (nullable, copy) NSString *name;
     @end
    
  2. NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
  3. 举例:

     #import "NSLockDemo.h"
    
     @interface NSLockDemo ()
        
     @property (nonatomic, strong) NSLock *moneyLock;
     @property (nonatomic, strong) NSLock *ticketLock1;
     @end
        
     @implementation NSLockDemo
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             self.moneyLock = [[NSLock alloc] init];
             self.ticketLock1 = [[NSLock alloc] init];
         }
         return self;
     }
     -(void)saleTicket{
         //尝试加锁,已经加锁就不走,没有加锁就加锁
         if ([self.ticketLock1 tryLock]) {
             [super saleTicket];
             //解锁
             [self.ticketLock1 unlock];
         }
     }
        
     -(void)drawMoney{
         //加锁
         [self.moneyLock lock];
         [super drawMoney];
         //解锁
         [self.moneyLock unlock];
     }
        
     -(void)saveMoney{
         //加锁
         [self.moneyLock lock];
         [super saveMoney];
         //解锁
         [self.moneyLock unlock];
     }
     @end
    
  4. NSRecursiveLock不在举例,因为跟NSLock用法一样

NSCondition、NSConditionLock

  1. NSCondition是对mutex和cond的封装

     @interface NSCondition : NSObject <NSLocking> {
     @private
         void *_priv;
     }
     //等待
     - (void)wait;
     //等待多长时间
     - (BOOL)waitUntilDate:(NSDate *)limit;
     //给等待发送信号
     - (void)signal;
     //给所有等待发送信号
     - (void)broadcast;
     @property (nullable, copy) NSString *name ;
     @end
    
  2. 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;
        
     @end
    
  3. 举例使用1(NSCondition)

     #import "NSConditionDemo.h"
     @interface NSConditionDemo ()
        
     @property (nonatomic, strong) NSCondition *condition;
     @property (nonatomic,strong) NSMutableArray *data;
     @end
     @implementation NSConditionDemo
        
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             self.data = [NSMutableArray array];
             self.condition = [[NSCondition alloc] init];
         }
         return self;
     }
     //删除数组中的元素
     -(void)__remove{
         //加锁
         [self.condition lock];
         NSLog(@"------remove");
            
         //一旦发现当前数组无元素就开始等待,让当前线程休眠。但是同时把锁打开,允许别的线程使用这把锁加锁
         if (self.data.count == 0) {
             //等待条件(进入休眠,放开_mutexLock锁;被唤醒后,会再次对_mutexLock锁住)
             [self.condition wait];
         }
            
         [self.data removeLastObject];
         NSLog(@"删除了元素");
         //解锁
         [self.condition unlock];
     }
     //往数组中添加元素
     -(void)__add{
         //加锁
        [self.condition lock];
         NSLog(@"------add");
         [self.data addObject:@"test"];
         NSLog(@"添加了元素");
         //激活一个等待该条件的线程
         //发信号通知等待(_cond),不用等了,唤醒等待的线程,让它继续执行
         [self.condition signal];
         //激活所有等待改条件的线程
         [self.condition broadcast];
         //解锁
         [self.condition unlock];
     }
     -(void)otherTest{
         [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
         sleep(1);
         [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
     }
     @end
    
  4. 举例使用2(NSConditionLock)

     #import "NSConditionLockDemo.h"
    
     @interface NSConditionLockDemo ()
        
     @property (nonatomic, strong) NSConditionLock *conditionLock;
     @property (nonatomic,strong) NSMutableArray *data;
     @end
     @implementation NSConditionLockDemo
        
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             self.data = [NSMutableArray array];
             //初始化一把锁,而且内部条件值为1
             self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
             //默认conditon值为0
     //        self.conditionLock = [[NSConditionLock alloc] init];
         }
         return self;
     }
     //删除数组中的元素
     -(void)__one{
         NSLog(@"------one-in---%@",[NSThread currentThread]);
         //加锁
         //当conditionLock的内部条件值(condition属性)为1的时候,而且当前锁没有加锁时,才加锁,否则就等待
         [self.conditionLock lockWhenCondition:1];
         NSLog(@"------one");
         //开锁
         //设置这把锁的条件值是2,并且把这把锁打开
         [self.conditionLock unlockWithCondition:2];
     }
     //往数组中添加元素
     -(void)__two{
         NSLog(@"------two-in---%@",[NSThread currentThread]);
         //加锁
         //当conditionLock的内部条件值(condition属性)为2的时候才加锁,而且当前锁没有加锁时,才加锁,否则就等待
         [self.conditionLock lockWhenCondition:2];
         NSLog(@"------two");
         //解锁 设置这把锁的条件值是3,并且把这把锁打开
         [self.conditionLock unlockWithCondition:3];
     }
     -(void)__three{
         NSLog(@"------three-in---%@",[NSThread currentThread]);
         [self.conditionLock lockWhenCondition:3];
         NSLog(@"------three");
         //解锁
         [self.conditionLock unlock];
     }
        
     //可以控制让3个县城按一定顺序执行
     -(void)otherTest{
         [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
         [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
         [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
     }
        
     @end
        
     //测试
     NSConditionLockDemo *demo6 = [[NSConditionLockDemo alloc] init];
     [demo6 otherTest];
    
    1. 打印:

       ------three-in---<NSThread: 0x60000194dc80>{number = 3, name = (null)}
       ------two-in---<NSThread: 0x60000194de40>{number = 4, name = (null)}
       ------one-in---<NSThread: 0x60000194e080>{number = 5, name = (null)}
       ------one
       ------two
       ------three
      
    2. 可以发现尽管是线程3先进入__three方法,但是执行的顺序仍然是按照one、two、three
    3. 因此,NSConditionLock可以控制线程执行的顺序

dispatch_queue

  1. 直接使用GCD的串行队列,也是可以实现线程同步的
  2. 线程同步本质就是按顺序执行任务,不让多条线程占用一份资源
  3. 举例使用:

     #import "SerialQueueDemo.h"
        
     @interface SerialQueueDemo ()
        
     @property (nonatomic, strong) dispatch_queue_t moneyQueue;
     @property (nonatomic, strong) dispatch_queue_t ticketQueue;
     @end
        
     @implementation SerialQueueDemo
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
             self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
         }
         return self;
     }
     -(void)saleTicket{
         dispatch_sync(self.ticketQueue, ^{
              [super saleTicket];
         });
     }
        
     -(void)drawMoney{
         dispatch_sync(self.moneyQueue, ^{
            [super drawMoney];
         });
     }
        
     -(void)saveMoney{
         dispatch_sync(self.moneyQueue, ^{
             [super saveMoney];
         });
     }
        
     @end
        
     //使用
     SerialQueueDemo *demo7 = [[SerialQueueDemo alloc] init];
     //    [demo7 saleTicketTest];
     [demo7 moneyTest];
    
    1. 这么也能达到目的
    2. 比如买票来说,可能多条线程相继访问买票方法saleTicket,但是多条线程的任务放在了一条队列中执行
    3. 这样就能保证,每个任务都是按顺序执行的,也达到了线程同步的效果

dispatch_semaphore

  1. semaphore叫做”信号量”
  2. 信号量的初始值,可以用来控制线程并发访问的最大数量
  3. 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

     //信号量的初始值
     int value = 1;
     //初始化信号量
     dispatch_semaphore_t  moneySemaphore = dispatch_semaphore_create(1);
     //如果信号量的值>0,就减1,继续执行代码
     //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0)
     dispatch_semaphore_wait(moneySemaphore, DISPATCH_TIME_FOREVER);
     //让信号量的值加1
     dispatch_semaphore_signal(moneySemaphore);
    
  4. 代码示例

    #import "SemaphoreDemo.h"
    @interface SemaphoreDemo ()
        
    @property (nonatomic, strong) dispatch_semaphore_t  moneySemaphore;
    @property (nonatomic, strong) dispatch_semaphore_t ticketSemaphore;
    @end
        
    @implementation SemaphoreDemo
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            //最大并发信号量是1,信号量的值是1
            self.moneySemaphore = dispatch_semaphore_create(1);
            //最大并发信号量是1,信号量的值是1
            self.ticketSemaphore = dispatch_semaphore_create(1);
        }
        return self;
    }
        
        
    -(void)saleTicket{
        //如果信号量的值>0,就减1,继续执行代码
        //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0)
        dispatch_semaphore_wait(_ticketSemaphore, DISPATCH_TIME_FOREVER);
        [super saleTicket];
        //让信号量的值加1
        dispatch_semaphore_signal(self.ticketSemaphore);
    }
        
    -(void)drawMoney{
        //如果信号量的值>0,就减1,继续执行代码
        //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0)
        dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER);
        [super drawMoney];
        //让信号量的值加1
        dispatch_semaphore_signal(self.moneySemaphore);
    }
        
    -(void)saveMoney{
        //如果信号量的值>0,就减1,继续执行代码
        //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0)
        dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER);
        [super saveMoney];
        //让信号量的值加1
        dispatch_semaphore_signal(self.moneySemaphore);
    }
        
        
        
    -(void)otherTest{
        for (int i = 0; i<10; i++) {
            [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
        }
    }
        
    //由于信号量为1, 那么每一个线程来调用,信号量就会为0了,下一个信号就不能进入了在外面等待,只有当前任务执行完毕,信号量+1,下一个线程才能进来
    - (void)test {
        //如果信号量的值>0,就减1,继续执行代码
        //如果信号量的值<=0,当前线程就会进入休眠状态等待(直到信号量的值>0)
        dispatch_semaphore_wait(_moneySemaphore, DISPATCH_TIME_FOREVER);
        sleep(3);
        NSLog(@"==test===%@====",[NSThread currentThread]);
        //让信号量的值加1
        dispatch_semaphore_signal(self.moneySemaphore);
    }
    @end
        
    //使用 
    SemaphoreDemo *demo8 = [[SemaphoreDemo alloc] init];
    //    [demo8 otherTest];
        
    //    [demo8 moneyTest];
    [demo8 saleTicketTest];   
    

@synchronized

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

      @synchronized (obj) {
     }
    
  4. 举例:

     #import "SynDemo.h"
     @implementation SynDemo
        
     -(void)saleTicket{
         static NSObject *lock;
         static dispatch_once_t onceToken;
         dispatch_once(&onceToken, ^{
             lock = [[NSObject  alloc] init];
         });
         @synchronized (lock) {
              [super saleTicket];
         }
     }
        
     -(void)drawMoney{
         @synchronized (self.class) {
            [super drawMoney];
         }
     }
     -(void)saveMoney{
         @synchronized (self.class) {
           [super saveMoney];
         }
     }
     @end
    

iOS线程同步方案性能比较

  1. 性能从高到低排序

     os_unfair_lock
     OSSpinLock
     dispatch_semaphore//常用
     pthread_mutex//常用
     dispatch_queue(DISPATCH_QUEUE_SERIAL)
     NSLock
     NSCondition
     pthread_mutex(recursive)
     NSRecursiveLock
     NSConditionLock
     @synchronized
    
  2. 自旋锁、互斥锁比较

    1. 什么情况使用自旋锁比较划算?
      1. 预计线程等待锁的时间很短
      2. 加锁的代码(临界区)经常被调用,但竞争情况很少发生
      3. CPU资源不紧张
      4. 多核处理器
    2. 什么情况使用互斥锁比较划算?
      1. 预计线程等待锁的时间较长
      2. 单核处理器
      3. 临界区有IO操作
      4. 临界区代码复杂或者循环量大
      5. 临界区竞争非常激烈
    3. 自旋锁相当于while循环线程一直处于忙碌状态,占用CUP资源。互斥锁线程休眠,不占用CUP资源

atomic

  1. atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
  2. 可以参考源码objc4的objc-accessors.mm

     void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
     {
         bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
         bool mutableCopy = (shouldCopy == MUTABLE_COPY);
         reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
     }
        
     static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
     {    
         ....
            
         if (!atomic) {
             oldValue = *slot;
             *slot = newValue;
         } else {
                
             spinlock_t& slotlock = PropertyLocks[slot];
             //加锁
             slotlock.lock();
             oldValue = *slot;
             *slot = newValue;
             //解锁
             slotlock.unlock();
         }
        
         objc_release(oldValue);
     }
        
     //get方法
     id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
         if (offset == 0) {
             return object_getClass(self);
         }
        
         // Retain release world
         id *slot = (id*) ((char*)self + offset);
         //不是automic直接返回
         if (!atomic) return *slot;
            
         //如果atomic
         // Atomic retain release world
         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);
     }
    
    
  3. 它并不能保证使用属性的过程是线程安全的

     #import <Foundation/Foundation.h>
     @interface Person : NSObject
     @property (atomic, assign) int age;
     @property (atomic,copy) NSString *name;
     @property (atomic,strong) NSMutableArray *data;
     @end
        
     //使用
      Person *person = [[Person alloc] init];
     //这两句是线程安全的
     //直接调用set方法,set方法有锁,所以是安全的
     person.data = [NSMutableArray array];
        
     //直接调用get方法,get方法内部也有锁,所以是安全的
     NSMutableArray *arr = person.data;
        
     //但是下面不是线程安全的:因为在get方法内部线程是安全的,一旦你通过get方法,拿到这个成员变量,然后设置成员变量,此时设置可以是多线程,就不是安全的了
     //atomic只能说是读/写安全的,不能保证成员变量的访问是安全的
     [arr addObject:@"1"];
     [arr addObject:@"2"];
     //person.data安全,addObject;不安全
     [person.data addObject:@"3"];
    
  4. 既然atomic生成的set、get方法是线程安全的,那为何平时不用呢? 却用nonatomic呢?
    1. 因为atomic太耗性能,我们平时用的get、set方法太频繁
    2. 手机内存本身就有限
    3. iOS开发大部分情况不会出现多条线程同时调用get、set方法

iOS中的读写安全方案

  1. 思考如何实现以下场景
    1. 同一时间,只能有1个线程进行写的操作
    2. 同一时间,允许有多个线程进行读的操作
    3. 同一时间,不允许既有写的操作,又有读的操作
  2. 上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有
    1. pthread_rwlock:读写锁
    2. dispatch_barrier_async:异步栅栏调用

pthread_rwlock(跨平台)

  1. 等待锁的线程会进入休眠
  2. 代码示例:

     #import "ViewController.h"
     #import <pthread.h>
     @interface ViewController ()
     @property (nonatomic, assign) pthread_rwlock_t rwLock;
     @end
        
     @implementation ViewController
        
     - (void)viewDidLoad {
         [super viewDidLoad];
            
         //初始化一把锁
         pthread_rwlock_init(&_rwLock, NULL);
     }
        
     -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
         dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
         for (int i = 0; i<5; i++) {
             dispatch_async(queue, ^{
                 [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
             });
             dispatch_async(queue, ^{
                 [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
             });
         }     
     }
        
     //实现多读
     -(void)read{
         //读-加锁
         pthread_rwlock_rdlock(&_rwLock);
         //读加锁尝试
     //    pthread_rwlock_tryrdlock(&_rwLock);
         sleep(1);
         NSLog(@"=====%s",__func__);
         //解锁
         pthread_rwlock_unlock(&_rwLock);
     }
        
     //实现一写
     -(void)write{
         //写-加锁
         pthread_rwlock_wrlock(&_rwLock);
         //写加锁尝试
     //    pthread_rwlock_trywrlock(&_rwLock);
         sleep(1);
         NSLog(@"=====%s",__func__);
          //解锁
         pthread_rwlock_unlock(&_rwLock);
     }
        
     -(void)dealloc{
         //销毁
         pthread_rwlock_destroy(&_rwLock);
     }
     @end
    
    
    1. 打印:

       2019-06-14 16:55:11.802254+0800 多线程-01[4062:313431] =====-[ViewController read]
       2019-06-14 16:55:12.807465+0800 多线程-01[4062:313432] =====-[ViewController write]
       2019-06-14 16:55:13.810705+0800 多线程-01[4062:313434] =====-[ViewController write]
       2019-06-14 16:55:14.811285+0800 多线程-01[4062:313437] =====-[ViewController write]
       2019-06-14 16:55:15.814354+0800 多线程-01[4062:313438] =====-[ViewController write]
       2019-06-14 16:55:16.818855+0800 多线程-01[4062:313439] =====-[ViewController read]
       2019-06-14 16:55:16.818855+0800 多线程-01[4062:313433] =====-[ViewController read]
       2019-06-14 16:55:17.823437+0800 多线程-01[4062:313440] =====-[ViewController write]
       2019-06-14 16:55:18.826876+0800 多线程-01[4062:313441] =====-[ViewController read]
       2019-06-14 16:55:18.826876+0800 多线程-01[4062:313436] =====-[ViewController read]
      
    2. 从上面的打印时间可以看出,read与read的时间可以是同时进行的(多读
    3. write与write的时间一定是不同时进行的(一写
    4. 而且read和write之间也一定不是同时进行的(一写

dispatch_barrier_async

  1. 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
  2. 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果
  3. 举例:

     #import "ViewController.h"
     @interface ViewController ()
     @property (nonatomic, strong) dispatch_queue_t queue;
     @end
        
     @implementation ViewController
        
     - (void)viewDidLoad {
         [super viewDidLoad];
            
         //必须是自己创建的,不能用全局并发队列(global)
         self.queue = dispatch_queue_create("barrqueue", DISPATCH_QUEUE_CONCURRENT);
     }
        
     -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
            
         for (int i = 0; i<5; i++) {
             [self read];
             [self read];
             [self write];
             [self write];
         }
     }
        
     - (void)test {
         dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
         for (int i = 0; i<5; i++) {
             dispatch_async(queue, ^{
                 [[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
             });
             dispatch_async(queue, ^{
                 [[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
             });
         }
     }
        
     //实现多读
     -(void)read{
         dispatch_async(self.queue, ^{
             sleep(1);
             NSLog(@"=====%s",__func__);
         });
           
     }
        
     //实现一写
     -(void)write{
         //执行这个时,会形成一个栅栏,不让别的线程进入执行
         dispatch_barrier_sync(self.queue, ^{
             sleep(1);
             NSLog(@"=====%s",__func__);
         });
     }
    
    1. 通过打印也能发现,读可以同时发生,写只能单次发生
Table of Contents