• iOS开发系列-线程同步技术


    概述

    多线程的本质就是CPU轮流随机分配给每条线程时间片资源执行任务,看起来多条线程同时执行任务。

    多条线程同时访问同一块资源,比如操作同一个对象、统一变量、同一个文件,就会引发数据错乱和数据安全的问题。

    多线程引发问题实例

    这里我也借助网上两个比较经典的案例,卖票和存取钱。

    卖票案例

    多个线程同时卖票,同一时间多个线层同时访问票的总数,就会引发数据错乱。

    实例代码

    @interface ViewController ()
    @property (nonatomic, assign) int tickets;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.tickets = 30;
        [self saleTicketTest];
    }
    
    - (void)saleTicketWithName:(NSString *)name
    {
       int currTicketCount = self.tickets;
        sleep(.2);
        currTicketCount--;
        self.tickets = currTicketCount;
        NSLog(@"当前%@卖了一张,票还剩:%d张", name ,self.tickets);
    }
    
    - (void)saleTicketTest
    {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
            for (int i=0; i<10; i++) {
                [self saleTicketWithName:@"A"];
            }
        });
        
        dispatch_async(queue, ^{
            for (int i=0; i<10; i++) {
                [self saleTicketWithName:@"B"];
            }
        });
        
        dispatch_async(queue, ^{
            for (int i=0; i<10; i++) {
                [self saleTicketWithName:@"C"];
            }
        });
    }
    @end
    

    异常结果:

    存钱取钱案例

    实例代码

    #import "ViewController.h"
    @interface ViewController ()
    @property (nonatomic, assign) int currMoney;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.currMoney = 1000;
        [self moneyTest];
    }
    
    - (void)moneyTest
    {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i=0; i<4; i++) {
                [self saveMoney];
            }
        });
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i=0; i<4; i++) {
                [self drawMoney];
            }
        });
    }
    
    // 存钱
    - (void)saveMoney
    {
        int money = self.currMoney;
        sleep(.3);
        money += 100;
        self.currMoney = money;
        NSLog(@"存了100,还剩%d元", self.currMoney);
        
    }
    
    // 取钱
    - (void)drawMoney
    {
        int money = self.currMoney;
        sleep(.3);
        money -= 50;
        self.currMoney = money;
        NSLog(@"取了50,还剩%d元", self.currMoney);
    }
    

    异常结果:

    多线程安全隐患的解决方案

    开发中解决多线程安全隐患的方案-使用线程同步技术。同步(协同步调,按预定的先后顺序执行)
    常见的线程同步技术就是加锁

    iOS中线程同步的方案根据性能优先级排序:

    线程同步的方案 需要导入头文件 类别 注意事项
    os_unfair_lock <os/lock.h> 互斥锁 iOS10才开始提供,用来替代OSSpinLock
    OSSpinLock <libkern/OSAtomic.h> 自旋锁 目前不在安全,可能会出现优先级反转问题
    dispatch_semaphore #import <pthread.h>
    pthread_mutex #import <pthread.h>
    dispatch_queue(DISPATCH_SENTINEL) #import <pthread.h>
    NSLock
    oNSCondition #import <os/lock.h>
    pthread_mutex(recuresive) #import <pthread.h>
    NSRecursiveLock
    NSConditionLock
    @synchronized

    OSSpinLock

    OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
    需要导入头文件#import <libkern/OSAtomic.h>

    os_unfair_lock

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

    pthread_mutex

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

    注意:pthread_mutex在不使用时需要释放锁与属性。

    在上面存钱取钱实例通过pthread_mutex实现线程安全。

    #import "ViewController.h"
    #import <pthread.h>
    @interface ViewController ()
    @property (nonatomic, assign) int currMoney;
    @property (nonatomic, assign) pthread_mutex_t lock;
    @property (nonatomic, assign) pthread_mutexattr_t attr;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // 初始化属性
    //    pthread_mutexattr_t att;
        pthread_mutexattr_init(&_attr);
        // 设置锁的类型
        pthread_mutexattr_settype(& _attr, PTHREAD_MUTEX_DEFAULT);
        
        // 初始化lock
        pthread_mutex_init(&_lock, & _attr);
        
        self.currMoney = 1000;
        [self moneyTest];
    }
    
    - (void)dealloc
    {
        pthread_mutex_destroy(& _lock);
        pthread_mutexattr_destroy(&_attr);
    }
    
    - (void)moneyTest
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i=0; i<4; i++) {
                [self saveMoney];
            }
        });
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i=0; i<4; i++) {
                [self drawMoney];
            }
        });
    }
    
    // 存钱
    - (void)saveMoney
    {
        pthread_mutex_lock(& _lock);
        int money = self.currMoney;
        sleep(.3);
        money += 100;
        self.currMoney = money;
        NSLog(@"存了100,还剩%d元", self.currMoney);
        pthread_mutex_unlock(& _lock);
    }
    
    // 取钱
    - (void)drawMoney
    {
        pthread_mutex_lock(& _lock);
        int money = self.currMoney;
        sleep(.3);
        money -= 50;
        self.currMoney = money;
        NSLog(@"取了50,还剩%d元", self.currMoney);
        pthread_mutex_unlock(& _lock);
    }
    

    pthread_mutex – 递归锁

    递归锁的特点:允许同一个线程对同一把锁进行重复加锁,这里强调的是同一个线程。

    递归锁在是使用场合是什么呢?

    方法嵌套

    - (void)test1
    {
        pthread_mutex_lock(&_lock);
        NSLog(@"任务1");
        [self test2];
        pthread_mutex_unlock(&_lock);
    }
    
    - (void)test2
    {
        pthread_mutex_lock(&_lock);
        NSLog(@"任务2");
        pthread_mutex_unlock(&_lock);
    }
    

    递归

    - (void)test1
    {
        pthread_mutex_lock(&_lock);
        NSLog(@"任务1");
        [self test1];
        pthread_mutex_unlock(&_lock);
    }
    

    pthread_mutex – 条件锁

    多线程任务执行是没有顺序的,比如现在想实现一个消费者模式的多线程案例
    当有生产者产出数据是才需要让消费者获取数据执行逻辑。由于生产与消费是多线程,可以在消费逻辑中加入判断,当没有数据时让消费线程休眠释放锁,生产者线程获取锁执行生产,当生产者有数据时发送信号通知消费者线程,消费者线程唤醒并重新再次加锁。
    这是就需要用pthread_mutex条件锁。

    实例代码

    #import "ViewController.h"
    #import <pthread.h>
    @interface ViewController ()
    @property (nonatomic, assign) pthread_mutex_t lock;
    @property (nonatomic, assign) pthread_cond_t condition;
    @property (nonatomic, strong) NSMutableArray *array;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.array = [NSMutableArray array];
    
        // 传入NUll 使用PTHREAD_MUTEX_DEFAULT类型的锁
        pthread_mutex_init(&_lock, NULL);
    
        pthread_cond_init(&_condition, NULL);
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self remove];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self add];
        });
    }
    
    // 删除数组中的元素
    - (void)remove
    {
        pthread_mutex_lock(&_lock);
        if (self.array.count == 0) {
            // 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
            pthread_cond_wait(&_condition, &_lock);
        }
        NSLog(@"删除了元素");
        [self.array removeLastObject];
        pthread_mutex_unlock(&_lock);
    }
    
    // 往数组添加元素
    - (void)add
    {
        pthread_mutex_lock(&_lock);
        [self.array addObject:@"test"];
        
        // 发送信号 激活等待该条件的线程
        pthread_cond_signal(&_condition);
        // 广播 激活所有等待该条件的线程
    //    pthread_cond_broadcast(&_condition);
        NSLog(@"添加了元素");
        pthread_mutex_unlock(&_lock);
    }
    

    NSLock NSRecursiveLock

    NSLock是对mutex普通锁的封装。遵守了协议。

    NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

    NSCondition

    NSCondition是对mutex和cond的封装。

    实例代码

    #import "ViewController.h"
    @interface ViewController ()
    @property (nonatomic, strong) NSMutableArray *array;
    @property (nonatomic, strong) NSCondition *condition;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.array = [NSMutableArray array];
        
         self.condition = [[NSCondition alloc] init];
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self remove];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self add];
        });
    }
    
    // 删除数组中的元素
    - (void)remove
    {
    //    pthread_mutex_lock(&_lock);
        [self.condition lock];
        if (self.array.count == 0) {
            // 执行wait 当前线层进入休眠,放开mutex锁,被唤醒后对mutex再次加锁
            [self.condition wait];
        }
        NSLog(@"删除了元素");
        [self.array removeLastObject];
        [self.condition unlock];
    //    pthread_mutex_unlock(&_lock);
    }
    
    // 往数组添加元素
    - (void)add
    {
    //    pthread_mutex_lock(&_lock);
        [self.condition lock];
        [self.array addObject:@"test"];
        
        // 发送信号 激活等待该条件的线程
        [self.condition signal];
        // 广播 激活所有等待该条件的线程
        [self.condition broadcast];
        NSLog(@"添加了元素");
        [self.condition unlock];
    //    pthread_mutex_unlock(&_lock);
    }
    

    NSConditionLock

    NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值。可以让子线程按着指定的顺序执行。

    示例代码:

    dispatch_semaphore 信号量

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

    dispatch_queue串行队列

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

    @synchronized

    @synchronized是对mutex递归锁的封装,@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

  • 相关阅读:
    php字符串处理函数大全 转
    php http Manual
    HTTP协议详解 百度文库
    PHP获取指定日期的上个月的日期 转
    计算指定日期的前N个月日期
    MySQL之count(*)与count(id)效率比较<转>
    生成器
    可迭代对象_迭代器
    变量内容的删除替换和替代
    软件磁盘阵列
  • 原文地址:https://www.cnblogs.com/CoderHong/p/9534713.html
Copyright © 2020-2023  润新知