并发模型之并发读写
将读写锁应用于并发读写
上一次学习 互斥和锁 http://blog.****.net/kangguang/article/details/51714767
这次主要学习 并发模型只并发读写相关的知识
GCD屏障允许在并行分发队列上创建一个同步的点。当遇到屏障时,GCD会延迟执行提交的代码块,直到队列中所有在屏障之前提交的代码块都执行完毕。随后,通过屏障提交的代码块会单独地执行。我们将这个代码块称为屏障块。待其完成后,队列会按照原有行为继续执行。
下图演示了屏障在多线程环境中执行的效果。块1到块6可以在多线程间并行执行。但是屏障块单独地执行。唯一的限制是,所有的执行都必须通过并行队列进行执行。
要想实现这一行为,我们需要遵循以下步骤。
(1)创建一个并行队列。
(2) 在这个队列上使用dispatch_sync执行所有的读操作。
在下面的代码中(3)在相同的队列上使用dispatch_barrier_sync执行所有的写操作。
实现高吞吐量且线程安全的模型。
程序运行 ,准确无误 但是一切看起来都很完美,但是如果需要访问一个正在修改都状态,那将会怎么样?
例如,如果缓存被清空,但因为用户执行了一个交互,其中部分状态要求立即被使用,将会出现一系列不可预测但问题。
解决办法:
使用不可变实体。
通过更新子系统提供支持。
允许观察者接收有关数据变化的通知。
这就创建了一个解耦的、可伸缩的系统来管理应用的状态。我们尝试从诸多可能的方案中选取一个予以实现。
首先要清晰地定义模型。在实例中,这里定义以下三个实体。
实现代码如下:
HPUser
表示系统中的一个用户。每个用户有唯一的 ID、拆分为firstName和lastName的姓名、性别和出生日期。HPAlbum
表示一个相册。每个用户有 0个或多个相册。每个相册包含唯一的***、持有者、名称、创建时间、描述、封面图片链接和点赞(喜欢该相册的用户)。HPPhoto
表示相册内的一张照片。每个相册可以包含 0 张或多张照片。每张照片包含唯一的***、所属的相册、用户(上传照片的人)、标题、url和大小(宽度和高度)。
@interface HPUser : NSObject
@property (nonatomic, copy) NSString *userId;
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSDate *dateOfBirth;
@property (nonatomic, strong) NSArray *albums;
@end
#import <Foundation/Foundation.h>
@class HPPhoto;
@class HPUser;
@interface HPAlbum : NSObject
@property (nonatomic, copy) NSString *albumId;
@property (nonatomic, strong) HPUser *owner;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString * descrip;
@property (nonatomic, copy) NSDate *creationTime;
@property (nonatomic, copy) HPPhoto *coverPhoto;
@end
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class HPUser;
@class HPAlbum;
@interface HPPhoto : NSObject
@property (nonatomic, copy) NSString *photoId;
@property (nonatomic, strong) HPAlbum *album;
@property (nonatomic, strong) HPUser *user;
@property (nonatomic, copy) NSString *caption;
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, assign) CGSize size;
@end
有多种方式可以定义填充数据的模型和机制。其中两个常见的方案是:
• 使用自定义的初始化器
•使用生成器模式每个方案各有优点。
使用自定义的初始化器意味着会有很长的方法名,从而导致令人讨厌的调用,比如方法initWithId:firstName:lastName:gender:birthday:。况且这只是我们使用模型中部分属性的情况。如果再加五个属性,初始化器会迅速膨胀。
自定义的初始化器还会带来向下兼容的问题。加入更多属性的新版模型无法向下兼容。但是,这也令使用了新版模型的应用能够在编译时知道什么地方发生了改变。
使用生成器模式需要引入外部类进行管理。生成器有setter方法,也需要提供与模型数据完全一致的存储。生成器最终也会使用初始化器。
模型地任何更新都需要对生成器及其背后对属性做出相应对改动。
推荐使用生成器模式,因为它支持向下兼容,而且即使不再加入新的属性,生成器也不会破坏应用。新版模型中的新增属性将继续持有其默认值。
代码实现:
User对象
构建对象
前面的代码具有以下优点
模型总是向下兼容。新版的模型生成器包含了新增属性,但不会破坏createUser的代码。
生成器可以被直接创建。模型的消费者可以初始化生成器,并调用build方法创建模型
对象。
生成器的创建和处理可以留给内部核心完成。模型的消费者可以使用类方法userWithBlock:
而无需初始化或亲自调用 build方法。
使用集中的状态更新服务
下一步我们需要一个更新服务,以更新客户端状态。更新服务可能需要连接服务器,在执行本地更新前进行验证,如加入或更新一条记录、确认好友的申请或上传一张照片。从UI的视角来看,在短暂的期间内,你需要向用户展示一个进度条或其他的指示器,以通知用户有关状态的实际情况。
在案例中,我们将HPUserService、HPAlbumService、HPPhotoService分别用于服务HPUser、HPAlbum、HPPhoto对象。
更新状态很棘手,因为状态是不可变的。有些矛盾,不是吗?解决办法是让状态的生成器接收一个后续可修改的输入状态。
为了对HPUser实现这一点,我们可以在HPUserBuilder上创建一个辅助初始化器,以便接收输入的对象。
例 s_1中的代码展示了改进后的HPUserBuilder类,以便支持对之前创建的HPUser对象进行修改,同时还创建了HPUserService类来获取和更新对象。类似的基础改造也适用于HPAlbum和HPPhoto实体。这段代码演示了用于用户和相册实体的服务,通常用于以下两个场景:
•从服务器获取数据并导致本地状态的更新;
•更新本地和远程的状态,例如,通过用户的交互。实体之间的交叉引用是一个需要注意的点。用户有一个相册列表,每个相册都有一个主人。类似地,相册中有照片列表,而每个照片都有它所隶属的相
册。我们甚至还没有为照片的评论建模,评论会包含写评论的时间、内容和写评论的作者。
不管它们是强还是弱,创建包含此类交叉引用的不可变对象被刻意忽略了。我们需要用户对象早于相册创建完成,反之亦然。这是个矛盾的局面。
解决之道是,如果没有具体标记为不可变,则保持对象可变。这就是所谓的冰棒不变性。要想实现这一点,你需要一个特殊的方法freeze或
markImmutable。为了能够使用这一结构,你需要自定义的setter方法,以便在允许修改前检查对象是否为不可变。
现在我们可以解决这个死锁。首先,让HPAlbum在设置持有者之前允许被修改。我们创建 HPUser对象,并设置HPAlbum对象的持有者。随后,
我们调用HPAlbum对象的freeze方法。当全部相册创建完成之后,我们将其设置为HPUser对象的albums属性。最终,我们调用HPUser对象的freeze方法。
代码效果如例 s_2所示。HPUser被更新为拥有读/写属性,并在标记为不可变之前是可变的。而且可以设想的是,绝大部分的使用场景都不再需要生成器
了,因为属性本身是读/写属性。
例 s_2 冰棒不可变实体
创建对象
虽然对象在短时间内可以被修改,但我们能够确保可变性是短暂的,且仅限于创建对象的线程。在将对象从创建方法送入需要它们的应用状态之前,你必须确保它们已经被标记为不可变。
状态观察者与通知
上面留给我们一个未解决的问题:如果对象被更新,那么我们应该如何同步更新依赖?或者换个角度追踪状态变更的最佳实践实践是什么?
要跟踪变更,做如下选择:
•键-值观察
•通知中心
•自定义的方案
键-值观察非常适合用来跟踪对象的属性。但是在我们的方案中,因为对象是不可变,而且我们要替换整个对象,所以键-值观察没有用武之地。因此,观察者不会收到任何的回调通知。
通知中心是优秀的解决方案。它提供了实用的功能,可以满足大多数的情况。但问题在于它最终会放大应用的复杂性。例如,按相册标识号码过滤更新通知或尽可能直接地将变化抛向UI。
这就需要自定义的解决方案出场了。要实现这个目标,我们将切换到响应式编程的风格。
响应式编程是基于异步数据流的编程方式。流是廉价且无处不在的,一切都可以是流:变量、用户输入、属性、缓存、数据结构,等等
ReactiveCocoa 库(https://github.com/ReactiveCocoa/ReactiveCocoa) 实 现 了 在Objective-C中进行响应式编程。它不仅可以实现对任意状态的观察,还提供
了高级的分类扩展,以便同步更新UI元素(如UILabel)或响应视图的交互(如UIButton)。
函数响应式编程与ReactiveCocoa应用通常会消费、制造和更新数据。响应式编程是一种编程范式,能够表示数据流,而无需担心副作用或对其他并发执行的任务造成影响。
响应式编程背后的核心思想是随时间流逝体现数据的值。使用动态值的数据流会导致这些值随着时间而变化。
函数响应式编程(functional reactive programming,FRP)允许响应式编程使用内建的块方法进行函数式编程,如map、reduce、filter、merge,等等。
ReactiveCocoa的灵感来自FRP。因为多组件的应用以高内聚的方式工作,所以组件状态的使用和更新就有了很强的关联。正因为这一点,创建一个低耦合、高内聚的响应式系统才显得尤为重要。
代码展示图下
使用这个库的主要动机是,它可以帮助我们实现一个观察变化的系统,这个系统有着低耦合、高伸缩性、自包含且适用于通用目的的特点。更重要的是,它为链接(使用RACSignal)提供了Promise,使得我们能够写出更易理解和维护的代码。它还提供了更加简便的方案以实现与UI元素的交互,