美文网首页
iOS 多线程

iOS 多线程

作者: 陈盼同学 | 来源:发表于2021-01-30 09:23 被阅读0次

    GCD的队列可以分为2大类型

    并发队列(Concurrent Dispatch Queue)

    可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    并发功能只有在异步(dispatch_async)函数下才有效(是因为一条线程没法做到并发)

    串行队列(Serial Dispatch Queue)

    让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    有4个术语比较容易混淆:同步、异步、并发、串行
    同步和异步主要影响:"能不能开启新的线程"
    同步:在当前线程中执行任务,"不具备"开启新线程的能力
    异步:在新的线程中执行任务,"具备"开启新线程的能力 (并不是一定会开启新线程,比如异步+主队列还会是在主线程 )

    并发和串行主要影响:"任务的执行方式"
    并发:多个任务并发(同时)执行
    串行:一个任务执行完毕后,再执行下一个任务

    主队列:主队列是一种特殊的串行队列

    六种组合

    |----------------------------------------------------------------------- |
    |           |   并发队列           |    手动创建的串行队列   |    主队列       |
    |----------------------------------------------------------------------- |
    |同步(sync)  |   没有开启新线程      |     没有开启新线程     |   没有开启新线程  |
    |           |    串行执行任务       |     串行执行任务      |    串行执行任务   |
    |----------------------------------------------------------------------- |
    |异步(async)|  有开启新线程         |   有开启新线程        | "没有开启"新线程  |
    |           |   并发执行任务        |     串行执行任务       |     串行执行任务 |
    |-----------------------------------------------------------------------
    
    

    dispatch_sync

    意味着将指定的block“同步”追加到指定的Dispatch Queue中,在追加block结束之前,dispatch_sync函数会一直等待。可以认为是简单版的dispatch_group_wait

    由于sync特性,所以使用sync函数往当前"串行"队列中添加任务,会卡住当前的串行队列(产生死锁)

    死锁的原因是:队列引起的循环等待。由于当前串行队列执行到dispatch_sync处等待dispatch_sync的返回,dispatch_sync必须等待block执行完毕后才能返回,由于当前队列是串行队列,先进先出,但是我们通过dispatch_sync新放入的block位于队列后面,现在得不到执行,所以锁住了。

    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    
    原因:
    因为把同步的任务2添加到主队列,dispatch_sync要求立马在当前线程同步执行任务,走到这一步想要立即执行。想要执行队列里的任务2,必须要等前面一整个任务执行完。任务3在等任务2,任务2在等任务3。
    
    用图表示为
    当前线程      |         主队列
    任务1        |      viewDidLoad
    sync        |       任务2
    任务3        |
    
    // 那么把上述的dispatch_sync换成dispatch_async会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    原因:
    dispatch_async不要求立马在当前线程同步执行任务
    //打印结果为 执行任务1  执行任务3  执行任务2
    
    
    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
    
    
    // 问题:以下代码是在线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
    
    
    // 问题:以下代码是在线程执行的,会不会产生死锁?不会!虽是同步,但确实并发队列
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"1");
        
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2");
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        
        NSLog(@"5");
        // Do any additional setup after loading the view.
    }
    
    //下面代码执行会打印什么
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
        
        //添加这行代码,启动RunLoop程序才能打印2
        //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
    });
    }
    - (void)test
    {
        NSLog(@"2");
    }
    
    答:会打印 1 3,但是2不会打印。因为performSelector:withObject:afterDelay:这句代码的本质是往Runloop中添加定时器,而子线程的Runloop在这里没有启动。
    注意:- (id)performSelector:(SEL)aSelector withObject:(id)object;等不需要Runloop。
    
    
    //下面代码执行会打印什么
    - (void)test
    {
        NSLog(@"2");
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSThread *thread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"1");
            
            //启动RunLoop,保住线程。然后程序正常运行,打印了2
            //[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }];
        [thread start];
        
        [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
    }
    答:会打印 1 ,然后程序崩溃。因为执行完1,block结束后线程结束。
    
    

    队列组

    在使用GCD进行任务操作时,有时会希望若干个任务执行之间有先后执行的依赖关系。

    eg:
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });
    
    //等前面的任务执行完毕后,会自动执行这个任务
    dispatch_group_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"任务3-%@", [NSThread currentThread]);
            }
        });
    });
    /*下面这是大牛讲解时
    //等前面的任务执行完毕后,会自动执行这个任务
    dispatch_group_notify(group, dispatch_get_main_queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"任务3-%@", [NSThread currentThread]);
            }
        });
    });
    */
    

    dispatch_set_target_queue

    用法一:指定优先级
    ?

    用法二:控制队列执行阶层
    如果在多个SERIAL Dispatch Queue中用dispatch_set_target_queue函数制定目标为某一个SERIAL Dispatch Queue,那么原本应并行执行的多个SERIAL Dispatch Queue,在目标SERIAL Dispatch Queue上只能同时执行一个处理。如下:

    //1.创建目标队列
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.将3个串行队列分别添加到目标队列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
    
    打印结果:
    2019-05-06 15:52:36.025069+0800 Test[4273:498926] 1 in
    2019-05-06 15:52:39.029506+0800 Test[4273:498926] 1 out
    2019-05-06 15:52:39.029782+0800 Test[4273:498926] 2 in
    2019-05-06 15:52:41.034457+0800 Test[4273:498926] 2 out
    2019-05-06 15:52:41.034623+0800 Test[4273:498926] 3 in
    2019-05-06 15:52:42.037019+0800 Test[4273:498926] 3 out
    
    

    dispatch_after

    下面代码3秒之后执行,可以是任何线程

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"waited at east three seconds.");
    });
    

    dispatch_group_notify

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"3");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done");
    });
    

    dispatch_group_wait

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"2");
        sleep(1.0);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"3");
    });
    
    //DISPATCH_TIME_FOREVER类型也是dispatch_time_t型
    //注意点,一旦调用dispatch_group_wait函数,该函数就处于调用状态而不返回。即是当前线程已经卡在这儿,不向后执行,必须等待前面任务处理有结果或者自己设定的dispatch_time_t时间结束。
    BOOL result =  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (result == 0) {//返回值为0,那么全部处理执行结束
        NSLog(@"done");
    }else{//如果时间过,但是前面的任务还没有跑完,会走到这儿
        NSLog(@"任务仍在执行");
    };
    

    dispatch_barrier_async

    //栅栏的队列不能使全局的并发,要是手动创建的并发  (待考证)
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    
    //barrier栅栏效果
    //官方解释:前面的执行完才到这,这个执行完才执行后面(★待考证)
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----5-----%@", [NSThread currentThread]);
    });
    
    

    dispatch_apply

    该函数按指定的次数将指定的block追加到指定的Dispatch Queue中,并等待全部处理结束。推荐在dispatch_async中非同步的执行dispatch_apply函数。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"----------------主线程开始------------");
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_async( queue, ^{
            
            NSLog(@"子线程%@",[NSThread currentThread]);
            
            dispatch_apply(3, queue, ^(size_t index) {
                
                NSLog(@"追加第%zu次任务",index);
            });
            
            NSLog(@"done");
            
            //可以回到主线程进行通信了
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"刷新界面");
            });
        });
        
        NSLog(@"----------------主线程继续------------");
    }
    
    打印结果:
    2019-05-07 10:16:06.001245+0800 Test[2820:134508] ----------------主线程开始------------
    2019-05-07 10:16:06.001436+0800 Test[2820:134508] ----------------主线程继续------------
    2019-05-07 10:16:06.001476+0800 Test[2820:134543] 子线程<NSThread: 0x600000c20a00>{number = 3, name = (null)}
    2019-05-07 10:16:06.001567+0800 Test[2820:134543] 追加第0次任务
    2019-05-07 10:16:06.001637+0800 Test[2820:134543] 追加第1次任务
    2019-05-07 10:16:06.001700+0800 Test[2820:134543] 追加第2次任务
    2019-05-07 10:16:06.001761+0800 Test[2820:134543] done
    2019-05-07 10:16:06.009918+0800 Test[2820:134508] 刷新界面
    

    dispatch_suspend和dispatch_resume

    线程的挂起和恢复。dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
        //提交第一个block,延时2秒打印。
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"After 2 seconds...");
        });
        //提交第二个block,也是延时2秒打印
        dispatch_async(queue, ^{
            sleep(2);
            NSLog(@"After 2 seconds again...");
        });
        //延时一秒
        NSLog(@"sleep 1 second...");
        sleep(1);
        //挂起队列
        NSLog(@"suspend...");
        dispatch_suspend(queue);
        //延时10秒
        NSLog(@"sleep 10 second...");
        sleep(10);
        //恢复队列
        NSLog(@"resume...");
        dispatch_resume(queue);
    }
    

    多线程的安全隐患

    资源共享
    1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
    比如多个线程访问同一个对象、同一个变量、同一个文件
    当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

    解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行。线程同步就是多个线程抢占同一份资源的时候,按顺序来抢占。线程同的本质就是不让多条线程同时占有一份资源)
    常见的线程同步技术是:加锁(并不是说只有加锁才能保证同步噢,比如串行队列等等啦)

    iOS中的线程同步方案

    OSSpinLock
    os_unfair_lock
    pthread_mutex
    dispatch_semaphore
    dispatch_queue(DISPATCH_QUEUE_SERIAL)
    NSLock
    NSRecursiveLock
    NSCondition
    NSConditionLock
    @synchronized
    

    题外话:

    "线程阻塞":想要让线程阻塞,常见的做法是让线程睡眠或者写个while循环一直等。
    "自旋锁"是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
    "互斥锁"是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。等待锁的线程会处于休眠状态。
    "递归锁"是同一线程能多次加锁,多次解锁。上面讨论的两种锁,都是同一线程在一次执行中只能对同一个锁加锁一次,否则会发生死锁。

    OSSpinLock (iOS10开始已经被弃用)

    OSSpinLock叫做"自旋锁",等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
    目前已经不再安全,可能会出现优先级反转问题。如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于OSSpinLock的忙等状态从而占用大量CPU。此时低优先级线程无法与高优先级线程争夺CPU时间,从而导致任务迟迟完不成、无法释放lock。

    需要导入头文件#import <libkern/OSAtomic.h>
    // 初始化锁
    OSSpinLock lock = OS_SPINLOCK_INIT;
    // 加锁
    OSSpinLockLock(&lock);
    //想做的事情
    代码...
    // 解锁
    OSSpinLockUnlock(&lock);
    

    os_unfair_lock (iOS10之后替代OSSpinLock的方案,解决了优先级反转问题。同时os_unfair_lock是一种互斥锁,它不会向自旋锁那样忙等,而是等待线程会休眠)

    需要导入头文件#import <os/lock.h>
    // 初始化锁
    os_unfair_lock lock= OS_UNFAIR_LOCK_INIT;
    // 加锁
    os_unfair_lock_lock(&lock);
    //想做的事情
    代码...
    // 解锁
    os_unfair_lock_unlock(&lock);
    

    pthread_mutex (叫做"互斥锁",等待锁的线程会处于休眠状态,可跨平台使用)

    需要导入头文件#import <pthread.h>
    //声明一个锁属性
    @property (assign, nonatomic) 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);
    
    //上面五句可以用下面这一句代替 初始化锁
    //pthread_mutex_init(&_mutex, NULL);
    
    
    //加锁
    pthread_mutex_lock(&_mutex);
    
    //想做的事情
    代码...
    
    //解锁
    pthread_mutex_unlock(&_mutex);
    
    //销毁锁
    pthread_mutex_destroy(&_mutex);
    

    pthread_mutex的类型由PTHREAD_MUTEX_DEFAULT换成PTHREAD_MUTEX_RECURSIVE就从互斥锁变成递归锁了,即允许用同一把锁重复加锁。

    递归锁:允许同一个线程对一把锁进行重复加锁。

    // 初始化属性
    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_mutex - 条件

    //增加锁的条件
    pthread_cond_wait //等待条件(代码执行到这儿进入休眠,放开mutex锁;被唤醒后,会再次对mutex加锁,代码从这儿开始往下执行)
    
    遇见需求多线程之间的依赖可以用这个
    

    NSLock、NSRecursiveLock

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

    NSLock

    //初始化锁
    NSLock *myLock = [[NSLock alloc] init];
    
    //加锁
    [myLock lock];
    
    //想做的事情
    代码...
    
    //解锁
    [myLock unlock];
    

    NSCondition

    NSCondition是对mutex和条件的封装

    - (void)wait;
    - (BOOL)waitUntilDate:(NSDate *)limit;
    - (void)signal; //信号
    - (void)broadcast; //广播
    

    NSConditionLock

    NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
    NSConditionLock 是锁,一旦一个线程获得锁,其他线程一定等待。
    [[NSConditionLock alloc]init];//普通初始化,此时如果想使用lockWhenCondition加锁,锁的条件默认是0
    [[NSConditionLock alloc]initWithCondition:<#(NSInteger)#>];//初始化并设置锁的条件
    [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁。
    [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
    [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件。
    return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理。
    

    dispatch_semaphore

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

    //定义一个控制最大量的值
    int value= 1;
    //初始化信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
    //执行到这句话时,如果信号量的值<=0,当前线程会进入睡眠等待(直到信号量的值>0);如果信号量的值>0,就减1,继续往下执行。
    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
    //让信号量的值加1
    dispatch_semaphore_signal(semaphore);
    

    @synchronized

    @synchronized是对mutex递归锁的封装
    源码查看:objc4中的objc-sync.mm文件
    打断点看到@synchronized会调用objc_sync_enter函数,源码里面查看objc_sync_enter一步步找
    @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

    @synchronized(obj){
        任务
    }
    

    针对线程同步方案的比较
    按性能排序(MJ总结)

        os_unfair_lock
        OSSpinLock
        dispatch_semaphore
        pthread_mutex
        dispatch_queue(DISPATCH_QUEUE_SERIAL)
        NSLock
        NSCondition
        pthread_mutex(recuivise)
        NSRecursiveLock
        NSConditionLock
        @synchronized
    

    YY作者排序一致:https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/

    自旋锁和互斥锁的比较

    什么时候使用自旋锁比较划算?
    1.预计线程等待锁的时间比较短;
    2.加锁的代码(也叫临界区)经常被调用,但竞争很少发生;
    3.CPU资源不紧张;
    4.多核处理器。

    什么时候使用互斥锁比较划算?
    1.预计线程等待锁的时间比较长;
    2.单核处理器;
    3.加锁的代码(也叫临界区)有IO操作(文件读取或写入);
    4.加锁的代码(也叫临界区)比较复杂或者循环量大
    5.加锁的代码(也叫临界区)竞争非常激烈

    atomic

    atomic用于保证属性setter、getter的原子性操作,相当于在setter和getter内部加了线程同步的锁,从而保证了setter和getter内部是线程同步的。

    可以参考源码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);
    }
    

    点击查看reallySetProperty方法

    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) {
            oldValue = *slot;
            *slot = newValue;
        } else { //如果是atomic,可以看到加了一个spinlock_t自旋锁
            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);
        if (!atomic) return *slot;
            
        // Atomic retain release world
        spinlock_t& slotlock = PropertyLocks[slot]; //如果是atomic,可以看到加了一个spinlock_t自旋锁
        slotlock.lock();
        id value = objc_retain(*slot);
        slotlock.unlock();
        
        // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
        return objc_autoreleaseReturnValue(value);
    }
    

    它并不能保证"使用属性"的过程是线程安全的。只是保证了set和get方法内部是线程安全的,但是并不能保证属性的过程是线程安全的。

    说atomic并不能保证使用属性的过程是线程安全的,

    举个例子
    1.对于NSArray类型 @property(atomic)NSArray *array我们用atomic修饰,数组的添加和删除并不是线程安全的,这是因为数组比较特殊,我们要分成两部分考虑,一部分是&array也就是这个数组本身,另一部分是他所指向的内存部分。atomic限制的只是&array部分,对于它指向的对象没有任何限制。
    eg:

    MJPerson *p = [[MJPerson alloc] init];//初始化类,类里面之前生命了一个可变数组dataArray属性
    p.dataArray = [NSMutableArray array];//初始化dataArray,这里面p.dataArray是线程安全的,因为调用了set方法
    [p.dataArray addObject:@"1"];  //这里面p.dataArray是线程安全的,因为调用了get方法,但是addObject却没有安全管控
    [p.dataArray addObject:@"2"];
    

    2、比如如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。

    atomic:原子性
    nonatomic:非原子性

    声明属性时不写的话默认就是atomic

    iOS中的读写安全方案

    IO操作:文件操作

    思考如何实现以下场景
    同一时间,只能有1个线程进行写的操作
    同一时间,允许有多个线程进行读的操作
    同一时间,不允许既有写的操作,又有读的操作

    上面的场景就是典型的"多读单写",经常用于文件等数据的读写操作,iOS中的实现方案有
    pthread_rwlock:读写锁
    dispatch_barrier_async:异步栅栏调用

    pthread_rwlock

    等待锁的线程会进入休眠

    导入#import <pthread.h>
    API如下
    //声明一把锁
    pthread_rwlock_t lock;
    //初始化锁
    pthread_rwlock_init(&lock, NULL);
    //读操作加锁
    pthread_rwlock_rdlock(&lock);
    //写操作加锁
    pthread_rwlock_wrlock(&lock);
    //操作之后解锁
    pthread_rwlock_unlock(&lock);
    //销毁锁
    pthread_rwlock_destroy(&lock);
    

    dispatch_barrier_async

    这个函数传入的并发队列必须是自己通过dispatch_queue-create创建的,全局并发队列或串行队列是没有效果的
    如果传入的是一个串行队列或是一个全局的并发队列,那这个函数便等同于dispatch_async 函数的效果

    相关文章

      网友评论

          本文标题:iOS 多线程

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