读写锁场景:

同一时间,只能有1个线程进行写的操作

同一时间,允许有多个线程进行读的操作

同一时间,不允许既有写的操作,又有读的操作

上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有:

1、读写锁:pthread_rwlock

等待锁的线程会进入休眠

// 导入头文件
#import <pthread.h>// 声明属性
@property (nonatomic, assign) pthread_rwlock_t rwlock;// 初始化
pthread_rwlock_init(&_rwlock, NULL);// 读加锁
- (void)read {pthread_rwlock_rdlock(&_rwlock);NSLog(@"read");pthread_rwlock_unlock(&_rwlock);
}// 写加锁
- (void)wtite {pthread_rwlock_wrlock(&_rwlock);NSLog(@"write");pthread_rwlock_unlock(&_rwlock);
}// 销毁锁
- (void)dealloc {pthread_rwlock_destroy(&_rwlock);
}

2、dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,全局队列不可以

#import "ViewController.h"@interface ViewController ()@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];[self readWriteLock];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {[self readWriteLock];// 重新生成一个vc,验证当前vc是否会产生循环引用,导致无法释放dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{UIViewController *vc = [[UIViewController alloc] init];vc.view.backgroundColor = [UIColor redColor];[UIApplication sharedApplication].keyWindow.rootViewController = vc;});}- (void)readWriteLock {// 使用自己创建的并发队列self.concurrentQueue = dispatch_queue_create("aaa", DISPATCH_QUEUE_CONCURRENT);// 使用全局队列,必定野指针崩溃
//    self.concurrentQueue = dispatch_get_global_queue(0, 0);// 测试代码,模拟多线程情况下的读写for (int i = 0; i<10; i++) {// 创建10个线程进行写操作dispatch_async(dispatch_get_global_queue(0, 0), ^{[self updateText:[NSString stringWithFormat:@"噼里啪啦--%d",i]];});}for (int i = 0; i<50; i++) {// 50个线程进行读操作dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"读 %@ %@",[self getCurrentText],[NSThread currentThread]);});}for (int i = 10; i<20; i++) {// 10个进行写操作dispatch_async(dispatch_get_global_queue(0, 0), ^{[self updateText:[NSString stringWithFormat:@"噼里啪啦--%d",i]];});}
}// 写操作,栅栏函数是不允许并发的,所以"写操作"是单线程进入的,根据log可以看出来
- (void)updateText:(NSString *)text {// block内不需要使用weakSelf, 不会产生循环引用dispatch_barrier_async(self.concurrentQueue, ^{self.text = text;NSLog(@"写操作 %@ %@",text,[NSThread currentThread]);// 模拟耗时操作,打印log可以放发现是1个1个执行,没有并发sleep(1);});
}
// 读操作,这个是可以并发的,log在很快时间打印完
- (NSString *)getCurrentText {__block NSString * t = nil;// block内不需要使用weakSelf, 不会产生循环引用dispatch_sync(self.concurrentQueue, ^{t = self.text;// 模拟耗时操作,瞬间执行完,说明是多个线程并发的进入的sleep(1);});return t;
}- (void)dealloc {// 页面可以正常释放NSLog(@"%s",__FUNCTION__);
}@end

参考Effective Objective-C . 书上用了全局队列, 实测全局队列的栅栏是无效的, 必定会导致野指针.算是书上的一个小bug吧.

下面在贴上一个AFN的实现 , requestHeaderModificationQueue是一个由AFHTTPRequestSerializer创建的并发队列, 使用这个并发队列对一个NSMutableDictionary实现了多读单写的锁.

    /// 并发队列self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);


对于读写锁的3个要求,  前2个都可以通过一个普通的锁来实现, 比如就通过NSLock在写的时候加锁, 这样可以满足条件1, 多个线程读的话, 那就不加锁来处理. 看上去一个普通的lock就可以满足"多读单写"的要求了, 那为什么还需要要求同一时间不能既有读操作,也有写操作呢?

  • 条件1: 同一时间,只能有1个线程进行写的操作
  • 条件2: 同一时间,允许有多个线程进行读的操作
  • 条件3: 同一时间,不允许既有写的操作,又有读的操作

我们使用这段代码测试下,

- (void)personDicTest {NSLock *lock = [[NSLock alloc] init];NSMutableDictionary * dic = [NSMutableDictionary dictionary];dispatch_async(dispatch_get_global_queue(0, 0), ^{while (1) {[lock lock];dic[@"1"] = [[Person alloc] init];[lock unlock];}});dispatch_async(dispatch_get_global_queue(0, 0), ^{while (1) {Person *p = [dic objectForKey:@"1"];NSLog(@"%@",p);}});
}

发现的确是不可以的, 只在写的时候加锁, 读的时候不加锁, 会发生crash,发生概率大约是1%, 前面的NSLog正常打印了97次, 在第98次打印时发生了野指针错误. 由于开启xcode的编译选项, 控制台输出了一条信息,给一个已经释放的对象发送了消息. 标准的野指针错误.

-[Person isProxy]: message sent to deallocated instance 0x107d75ac0

如果在读操作时也加上锁, 那么就不会有问题了, 但是这样就不满足 "多读单写"的要求了,

    dispatch_async(dispatch_get_global_queue(0, 0), ^{while (1) {[lock lock];Person *p = [dic objectForKey:@"1"];NSLog(@"%@",p);[lock unlock];}});

所以想要满足"同一时间多读单写"的读写锁,方案只有pthread_rwlock/dispatch_barrier_async, 普通的锁无法满足要求.


上面的测试又带了一个新问题, 通过实验知道只在写操作加锁不能满足要求, 那为什么不能满足要求呢? 是什么导致的会crash呢?

分析下读写操作中都发生了什么?

写操作, 线程A : NSLock加锁 -- 从字典中找出旧值 -- 调用旧值的release方法 -- 设置新值 -- 调用新值的 retain方法, 增加引用计数 -- NSLock解锁

读操作, 线程B : 从字典中查到key为@"1"的对象地址 -- 对此地址进行一次retain -- 调用[obj autoRelease] 返回此地址 -- 外界使用局部变量 *p接受此地址 -- 调用一次 [p retain] -- 使用此值做事情,.... -- 局部变量作用域消失, 调用 [p release]

由于读操作没有加锁, 2个线程可能会同时执行, 那么就有可能发生 线程B先从地址中取出值, 线程A调用release方法, 这个时候这个对象的引用计数就已经是0, 这个地址是野指针了.

如果给读操作加上锁, 那么这部分代码就不会有多线程进入了, 也就不会发生野指针了. 看似锁里只有一行代码, 但是在运行时拆解成了很多汇编指令.

ios实现读写锁,AFN的实现相关推荐

  1. 将读写锁放到共享内存,实现进程之间对于同一文件的读写操作

    思路 将读写锁和读写锁的属性以及一个用于存储共享内存的地址的int型变量三者封装成一个struct结构 将这个结构体放到共享内存中,以及将读写锁的属性设置成全局性质,然后使用这个属性初始化锁,以及将锁 ...

  2. java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)

    前言 本文对Java的一些锁的概念和实现做个整理,涉及:公平锁和非公平锁.可重入锁(又名递归锁).自旋锁.独占锁(写)/共享锁(读)/互斥锁.读写锁 公平锁和非公平锁 概念 公平锁是指多个线程按照申请 ...

  3. ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  4. golang:1.并发编程之互斥锁、读写锁详解

    本文转载自junjie,而后稍作修改. 一.互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段.它由标准库代码包sync中的Mutex结构体类型代表.sync.Mutex类型(确切地说,是 ...

  5. Linux多线程的同步------读写锁

    前面介绍过Linux多线程同步的另外两个方法------互斥锁和信号量 Linux多线程的同步-----信号量和互斥锁_神厨小福贵!的博客-CSDN博客 下面来看一下读写锁: 读写锁和互斥锁都带有一个 ...

  6. 互斥量、读写锁长占时分析的利器——valgrind的DRD

    在进行多线程编程时,我们可能会存在同时操作(读.写)同一份内存的可能性.为了保证数据的正确性,我们往往会使用互斥量.读写锁等同步方法.(转载请指明出于breaksoftware的csdn博客) 互斥量 ...

  7. Linux多线程实践(6) --Posix读写锁解决读者写者问题

    Posix读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restr ...

  8. ReentrantReadWriteLock(读写锁)

    为了提高性能,java提供了读写锁, 读锁: 在读的地方使用读锁,可以多个线程同时读. 写锁: 在写的地方使用写锁,只要有一个线程在写,其他线程就必须等待 例子: public static Read ...

  9. Java并发- 读写锁中的性能之王:StampedLock

    为什么StampedLock这么神奇?能够达到这种效果,它的核心思想在于,在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作.这种模式也就是典型的无锁编程思想,和CAS自旋的思 ...

  10. 嵌入式 自旋锁、互斥锁、读写锁、递归锁

    互斥锁(mutexlock): 最常使用于线程同步的锁:标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁:临界区和互斥量都可用来实现此锁,通常情况下锁操作失败会将该线程 ...

最新文章

  1. nfine框架 上传文件_网站服务器Nginx运行环境,后台文件上传超出Nginx的最大值...
  2. java在dog中定义name变量,组合构造 冯跃峰 java中组合的应用(不相干的类共同完成一个功能)+构造器回顾...
  3. 电脑技巧:键盘失灵怎么办?
  4. [html] 编写html时,你有没有用过Emmet插件呢?说说它的优点及规则有哪些?
  5. 可以用什么代替平面镜
  6. 记忆化搜索 codevs 2241 排序二叉树
  7. 单片机 驱动 标签打印机tsc_指令打印与驱动打印随笔
  8. python代码示例-《Python编程:从入门到实践》DEMO实例代码
  9. 000+0000 格式的时间转成 年月日
  10. 2021-10-14 矩阵求导相关
  11. 阿里笔试题:求两个子序列的最大连续子序列
  12. 如何添加、管理和删除PDF高亮文本?
  13. linux设置ipsan_linux 配置SAN存储-IPSAN
  14. java代码实现一个月内不再提醒,通用到期问题
  15. STM32MP157 三角函数运算花费时间比较
  16. 计算机用户无法删除文件,教你几招解决电脑上的文件夹删不掉怎么办?
  17. rust倒地了怎么起来_ggxx出招表
  18. 普元mobile_普元Primeton Mobile 7.1 正式发布 互联网集成能力加速企业数字化转型
  19. 小程序: getPhoneNumber功能详解,获取手机号登录 2019
  20. 大数据必学Java基础(三十一):IDEA模板的使用

热门文章

  1. mysql查找数据库文件位置
  2. viper4android ddc,蝰蛇音效v4a音效最新版
  3. Appium 简明教程
  4. 关于intel六代/七代CPU安装win7系统解决USB3.0驱动的镜像文件
  5. Labelimg图像标注
  6. 4.6 Data符号调制——16QAM
  7. 如何在Java项目中定义并调用自己编写的native方法?
  8. 医咖会免费SPSS教程学习笔记—斯皮尔曼相关系数(秩相关系数)
  9. 手机号码校验正则表达式
  10. 阿里代码规范检查工具的安装使用