题目
设计线程安全的惰性求值容器
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
线程安全, 惰性求值, 元编程, 高级对象设计
快速回答
实现线程安全的惰性求值容器需要:
- 使用
Mutex或Monitor保证线程安全 - 通过
@calculated标志位避免重复计算 - 实现
#value方法触发首次计算 - 处理计算异常并提供重置机制
- 使用
define_method动态生成访问器
核心需求与原理
设计一个容器,存储代码块并在首次访问时执行计算(惰性求值),后续访问直接返回缓存结果。关键挑战:
- 线程安全:多线程同时访问时需保证计算只执行一次
- 异常处理:计算失败时需记录异常状态
- 动态接口:支持通过方法名访问计算结果
完整实现代码
require 'monitor'
class LazyContainer
include MonitorMixin
def initialize
super()
@values = {}
@mutex = new_cond
end
def define_lazy(name, &block)
synchronize do
return if @values.key?(name)
@values[name] = {
block: block,
calculated: false,
value: nil,
error: nil
}
end
define_singleton_method(name) do
entry = @values[name]
synchronize do
unless entry[:calculated]
begin
entry[:value] = entry[:block].call
rescue => e
entry[:error] = e
ensure
entry[:calculated] = true
end
end
raise entry[:error] if entry[:error]
entry[:value]
end
end
define_singleton_method("reset_#{name}") do
synchronize do
@values[name][:calculated] = false
@values[name][:error] = nil
end
end
end
end使用示例
# 初始化容器
container = LazyContainer.new
# 定义惰性值(模拟耗时计算)
container.define_lazy(:heavy_calculation) do
sleep(2)
rand(100)
end
container.define_lazy(:api_call) do
raise 'Network error' if rand > 0.5
{ data: 'API response' }
end
# 多线程访问
threads = []
5.times do
threads << Thread.new do
puts container.heavy_calculation
rescue => e
puts "Error: #{e.message}"
end
end
threads.each(&:join)
# 重置值
container.reset_heavy_calculation关键技术点
- 线程同步:使用
MonitorMixin替代Mutex支持嵌套锁,避免死锁 - 双重检查锁定:在同步块内二次检查
calculated状态,确保线程安全 - 异常传播:缓存首次计算的异常,后续访问直接抛出相同异常
- 动态方法:通过
define_singleton_method动态创建访问器和重置方法 - 状态管理:使用
calculated标志位分离未计算/已计算/异常三种状态
常见错误
- 非原子检查:未同步检查
@calculated导致多次计算 - 异常吞没:未妥善处理异常导致后续访问静默失败
- 锁粒度不当:过大锁粒度引发性能瓶颈,过小导致竞态条件
- 内存泄漏:长期持有计算块引用阻止GC回收
最佳实践
- 优先使用
Monitor而非原始Mutex(支持重入) - 为每个惰性值单独存储状态,避免全局锁争用
- 提供
reset_*方法支持重新计算 - 考虑引入超时机制防止死锁
- 复杂场景可改用
Concurrent::Promise(concurrent-ruby gem)
扩展知识
- GIL 影响:MRI Ruby 的 GIL 不保证线程安全,IO 操作期间会释放锁
- 惰性模式对比:
||=操作符非线程安全,Rails的memoize非线程安全 - 函数式编程:Haskell 等语言原生支持惰性求值,Ruby 需手动实现
- 性能优化:使用
Atomic标记可减少锁竞争(适用于读多写少场景)