美文网首页
谈谈iOS的线程安全

谈谈iOS的线程安全

作者: 码工人生 | 来源:发表于2023-02-11 17:30 被阅读0次

    一、先说什么情况下线程是不安全的

    在同一个进程里,资源是可以共享的;多个线程同时访问相同资源,并且至少有一个线程在进行写操作。

    如以下情况:
    a、多个线程同时访问一个变量:

    单例的某个公共属性(如:NSMutableDictionary),可能被某个线程在写,也可能正在被另外线程在读;此时包括NSMutableDictionary里面的key对应的对象,以及对象里的属性都不是安全的。

    b、多个线程访问沙盒里的某个文件:

    一个线程正在下载该文件,另外一个可能正在读取该文件;该文件就是不安全的。

    总之,只要你在coding的过程里,有异步操作,且有读写某个全局型资源(变量、对象、文件等等),都需要考虑线程安全。

    二、如何做到线程安全?

    1、使用前进行完整性校验

    这个解决方案适用于有些场景无法加锁的情况下。如,写操作发生在OC代码里,读操作发生在某个sdk的C++代码里,你没法去改sdk的代码。所以这个时候,就要做读取前校验,如通过md5校验文件的完整性等。

    2、锁

    介绍iOS锁的文章很多,这里主要介绍两种常见的锁。

    I、@synchronized,使用起来最简单的锁

    @synchronized(obj) {
            // 需要加锁的代码块
    }
    
    @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对obj对象进行修改;

    这个是objective-c的一个锁定令牌,防止obj对象在同一时间内被其它线程访问,起到线程的保护作用。

    指令@synchronized()通过对一段代码的使用进行加锁。其他试图执行该段代码的线程都会被阻塞,直到加锁线程退出执行该段被保护的代码段,也就是说@synchronized()代码块中的最后一条语句已经被执行完毕的时候。

    obj对象就是一个互斥信号量。

    @synchronized(obj)使用起来很简单,括号里的obj对象要注意以下事项:
    a、obj对象的创建时机要早于多线程调用

    一般在构造方法(init)方法里就创建好。

    b、obj对象的创建时机要早于多线程调用
    c、obj对象的创建时机要早于多线程调用
    d、不要使用@synchronized(self)

    容易导致死锁的出现:

    ClassA:

    @synchronized (self) {
        [_sharedLock lock];
        NSLog(@"code in class A");
        [_sharedLock unlock];
    }
    

    ClassB:

    [_sharedLock lock];
    @synchronized (objectA) {
        NSLog(@"code in class B");
    }
    [_sharedLock unlock];
    

    在ClassB中使用了ClassA的实例当token,ClassA中使用self当key,都是一个实例。两个公共锁交替使用同一个key,导致死锁。

    e、obj最好是当前类内部维护的一个NSObject对象,对外不可见
    f、不同的数据使用不同的锁,避免性能浪费
    @synchronized (objA) {
        [arrA addObject:obj];
    }
    
    @synchronized (objB) {
        [arrB addObject:obj];
    }
    
    g、加锁的范围越小越好
    @synchronized (tokenA) {
        [self doSomethingWithA:arrA];
    }
    - (void)doSomethingWithA {
        NSArray *arrA = [[NSArray alloc]init];
        [_arrB addObject:objB];
        [self doSomethingWithB];
    }
    

    其实我们只需要对[_arrB addObject:objB]这行代码加锁就可以。

    ||、dispatch_semaphore,性能最好的锁

    //创建信号量,参数:信号量的初值,如果小于0则会返回NULL
    dispatch_semaphore_create(信号量值)
     
    //等待降低信号量
    dispatch_semaphore_wait(信号量,等待时间)
     
    //提高信号量
    dispatch_semaphore_signal(信号量)
    

    见YYCache的一段源码,通过dispatch_semaphore实现set和get的安全访问

    static void _YYDiskCacheInitGlobal() {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _globalInstancesLock = dispatch_semaphore_create(1);
        });
    }
    
    
    static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
        if (path.length == 0) return nil;
        _YYDiskCacheInitGlobal();
        dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
        id cache = [_globalInstances objectForKey:path];
        dispatch_semaphore_signal(_globalInstancesLock);
        return cache;
    }
    
    static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
        if (cache.path.length == 0) return;
        _YYDiskCacheInitGlobal();
        dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
        [_globalInstances setObject:cache forKey:cache.path];
        dispatch_semaphore_signal(_globalInstancesLock);
    }
    
    参考文章

    https://juejin.cn/post/6844903890056396814

    https://blog.51cto.com/u_15316122/3210257

    https://blog.csdn.net/u013756604/article/details/109510282

    相关文章

      网友评论

          本文标题:谈谈iOS的线程安全

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