线程不安全的结果也就是validator很可能会出错。
对一个类C,配置一个validator类VC,而且假设rails每次都保证在为每一个C对象创建一个新的VC对象。
假设类VC里面定义def validate(c),且类C对象存在内存堆里面。
例如类C是房子,一个房子里面会住人,VC负责检测这个房子里面不能低于2个人。
那么,在c对象本身可以被VC1和VC2访问的情况下,假如VC1和VC2同时访问c对象,
VC1稍微快一点,这个时候c里面只有一个人,所以VC1检测返回失败;
VC2稍后,但这个时候房子c里面来了一个人(例如被另一个线程写操作修改),这个时候,VC2的检测返回成功。
这样,系统报出VC1的检测返回失败,这样会对整个系统造成困扰。
想象一下这种情形:
(1)假设类VC里面定义了:attr_reader :c
(2)执行各种具体验证方法的时候,里面调用@c
(3)执行一个具体验证方法的时候,VC1的@c被另一个线程VC2替换掉了,从c1变成了c2,导致验证失败
这就是rails validator的最佳实践:在不能保证c不变的时候,还要尽量线程安全,那么只能从validator对象进行改造,保证 validator线程有自己的独立副本。
首先,rails官方文档里面说validator的实例变量只会被初始化一次,the validator will be initialized only once for the whole application life cycle, and not on each validation run, so be careful about using instance variables inside it.
也就意味着validator的实例是一种类单例,会被rails缓存下来使用。
这么理解:
class ActiveModel::Validations::ClassMethods def validates_with(*validators) validators.each do |validator_class|
validator_instance = validator_instances[validator_class] ||= validator_class.new
end
优点是: 内存效率:避免为每次验证创建新实例 性能优化:初始化只发生一次 简单的 API:开发者不需要关心实例管理
缺点:线程容易不安全, 例如,如果使用attr_reader :c,@c这种堆对象,那么在验证不同的c的的时候,c2会覆盖之前的c1,也就是覆盖掉@c,导致之前的验证中途因为@c的变化而可能失败。
稍微的线程安全:保证validator线程有自己的独立副本,如何保证呢?不使用堆对象,而改用栈对象,也就是在执行所有具体验证方法的时候,不是使用@c,而是显示的传入c作为参数,
作为参数后,c是存在栈(线程栈)上面的,每个线程有自己的副本。