多线程读写安全的解决办法
2020-10-25 本文已影响0人
Sweet丶
对于同一个资源,如果多个线程同时执行"写"操作,那么就容易造成资源的"写"操作出错;如果在写入的过程中进行"读"的操作,那么就可能读取了"写"操作执行到一半的东西,这两者都不是我们想要的,所以就有了读写安全的方案:
- 在没有“写”操作执行中时,允许多个线程同时”读”。
- 在有“读”操作时,“写”操作可以执行,但这个“写”执行时,不允许再有“读”和“写”操作同时执行,必须等这个“写”执行完之后才可以。
iOS 中实现上述思路的方案有两个:
-
pthread_rwlock: 系统库#import <pthread/pthread.h>里面的函数, 有对应的“读”锁和“写”锁。 -
dispatch_barrier_async:GCD的栅栏函数,这个函数能让此任务添加到并发队列后,先执行完之前添加的任务,再执行此任务,等这个任务执行之后最后再执行后面添加到对应queue的任务。所以读跟写的操作都放在同一个队列中,其中“写”任务用dispatch_barrier_async来添加就能实现读写安全。
具体使用:
一、dispatch_barrier_async
+ (void)barrierTest{
dispatch_queue_t queue = dispatch_queue_create("ReadWriteLock_con", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {// 将10个读取任务添加到queue中
dispatch_async(queue, ^{
NSLog(@"读任务执行------“写”前");
sleep(0.5);
});
}
for (int i = 0; i < 3; i++) {// 将3个写任务添加到queue中
dispatch_barrier_async(queue, ^{
NSLog(@"写任务执行------“写”");
sleep(3);
});
dispatch_async(queue, ^{
NSLog(@"读任务执行------“写”后");
sleep(0.5);
});
dispatch_async(queue, ^{
NSLog(@"读任务执行------“写”后");
sleep(0.5);
});
}
}
打印:
2020-10-25 14:05:10.397 KVO[10978:1023342] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023354] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023355] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023361] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023363] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023350] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023362] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023359] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023360] 读任务执行------“写”前
2020-10-25 14:05:10.397 KVO[10978:1023364] 读任务执行------“写”前
2020-10-25 14:05:10.398 KVO[10978:1023364] 写任务执行------“写”
2020-10-25 14:05:13.402 KVO[10978:1023364] 读任务执行------“写”后
2020-10-25 14:05:13.402 KVO[10978:1023360] 读任务执行------“写”后
2020-10-25 14:05:13.403 KVO[10978:1023360] 写任务执行------“写”
2020-10-25 14:05:16.407 KVO[10978:1023360] 读任务执行------“写”后
2020-10-25 14:05:16.407 KVO[10978:1023364] 读任务执行------“写”后
2020-10-25 14:05:16.408 KVO[10978:1023364] 写任务执行------“写”
2020-10-25 14:05:19.412 KVO[10978:1023364] 读任务执行------“写”后
2020-10-25 14:05:19.412 KVO[10978:1023360] 读任务执行------“写”后
关键点:
- 传入的queue需要是自己创建的并发队列,不能是串行队列
DISPATCH_QUEUE_SERIAL或者是全局的并发队列dispatch_get_global_queue. - 从打印中看出
dispatch_barrier_async添加的“写”任务需要等前面的10个“读”任务都执行完才开始,“写”任务后面的2个“读”任务要等前面的“写”执行完才开始。 - 缺点:读写任务都要放在同一个queue否则也是没有用的。
二、pthread_rwlock
- (void)testPthreadrwlock{
pthread_rwlock_init(&(_rwlock), NULL);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i<10; i++) {// 将5个读取任务添加到queue中
dispatch_async(queue, ^{
[self read];
});
}
for (int i = 0; i < 3; i++) {// 将3个写任务添加到queue中
dispatch_async(queue, ^{
[self write];
});
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
}
}
- (void)read{
pthread_rwlock_rdlock(&_rwlock);
NSLog(@"读操作");
sleep(0.5);
pthread_rwlock_unlock(&_rwlock);
}
- (void)write{
pthread_rwlock_wrlock(&_rwlock);
NSLog(@"写操作");
sleep(3);
pthread_rwlock_unlock(&_rwlock);
}
- (void)dealloc{
pthread_rwlock_destroy(&_rwlock);
}
打印结果跟dispatch_barrier_async是一样的
关键点:
- 这是一个跨平台的方案。
使用读写锁pthread_rwlock_t