iOS 多线程

2021-01-30  本文已影响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 函数的效果

上一篇下一篇

猜你喜欢

热点阅读