这是一个教学示例,旨在说明在F#中可变和列表的使用。该结构是一种线程安全堆栈结构,多个线程可以同时推送到/pop。关切事项:
代码如下所示:
type ConcurrentStack<'T>() =
let mutable _stack : List<'T> = []
member this.Push value =
lock _stack (fun () ->
_stack <- value :: _stack)
member this.TryPop() =
lock _stack (fun () ->
match _stack with
| result :: newStack ->
_stack <- newStack
result |> Some
| [] -> None
)发布于 2014-04-27 14:59:31
这真的是线程安全吗?
是的,我想会的。对_stack字段的所有访问都受到锁的保护,所以我不明白为什么这不是线程安全。
使用不可变列表的一个很好的巧合是,您还可以很好地实现IEnumerable<'T>,甚至返回堆栈中当前所有元素的列表--因为数据类型是不可变的,所以它可以安全地与其他线程共享(这意味着,您不会遇到"Collection;枚举操作不能继续“的常见麻烦,您可以在C#中获得这些操作。
锁有必要吗?
我也这么想。如果没有锁,一个线程可以通过计算Push开始计算value::_stack,然后另一个线程可以修改_stack,然后原始线程会用旧的value::_stack值覆盖_stack (这样,另一个线程的效果就会消失!)
是否有一种不变的方法来做同样的事情?我不认为在这里可以使用传统的不可变方法,即让Push/Pop返回一个新的堆栈实例,因为不同的客户端会看到不同的堆栈视图。
如果想法是让多个线程共享状态,那么它们就需要某种方式来共享&改变状态。F#中的另一种方法是使用代理并将当前的_stack保持为异步循环的参数--在这种情况下,您不会在编写的F#代码中使用突变(但是代理中存在隐藏的变异和同步)。
从逻辑上讲,基于代理的版本与您使用锁的版本非常相似--由于其他原因(获得正确的锁很难,代理不可能执行相同的错误),这可能会更好一些,但这取决于您想要说明什么!
发布于 2014-04-27 17:01:23
锁有必要吗?
一些同步是必要的,但它不一定是一个锁。
另一种选择是使用Interlocked.CompareExchange。但是这样做要复杂得多,所以我会坚持使用锁,除非分析表明锁是减慢代码速度的原因。
https://codereview.stackexchange.com/questions/48335
复制相似问题