大多数语言都提供原子int
操作的功能(添加,比较和交换等)。
为什么不对浮动类型?
答案 0 :(得分:5)
从操作系统/硬件设计的角度来看,让我们思考浮点原子......
存在原子,因为它们需要同步。大多数同步涉及什么?句柄,标志,互斥锁,自旋锁 - 只要每个用户一致且用户之间不同,其实际价值毫无意义的东西。即使像信号量这样的值更有意义的东西 - 它仍然是计算而不是测量,所以无论我们认为它代表什么,32位都值32位。 / p>
其次,技术问题。我们可以编程的任何东西都可以进行整数运算。不是那么浮点 - 当C操作由C库模拟时,那些原子将在困难和不可能实现之间。即使在硬件中,FP操作通常会比整数慢,谁想要慢速锁? FPU本身的设计甚至可能使实现原子操作变得困难 - 例如如果没有任何直接访问内存总线的情况下挂起协处理器接口。
第二个半,如果我们想要float
,我们当然也需要double
吗?但double
通常存在比机器字更大的问题,排除了在许多架构上均匀加载和存储的原子性。
第三,当谈到像原子一样的东西时,CPU架构师倾向于实现系统设计人员和操作系统人员要求的东西,操作系统人员并不完全喜欢一般的浮点 - 愚蠢的额外寄存器来保存,减慢上下文切换...硬件中的更多指令/功能会降低功耗和复杂性,如果客户不需要它们......
因此,简而言之,用例不够用,因此没有硬件支持,因此没有语言支持。当然,在某些架构上你可以roll your own atomics,我认为GPU计算可能对主要浮点硬件的同步有更多的需求,所以谁知道它是否会保持这种状态?
答案 1 :(得分:1)
啊,最初的问题是用Go标记的,并且略有不同,所以这就是我要回答的问题,对不起,如果这不是编辑问题的完整答案:)
使用Go,您可以原子地交换任何指针值,并在不安全的黑暗面上进行一次小行程:
http://play.golang.org/p/aFbzUMhfEB
摘录:
var uptr unsafe.Pointer
var f1, f2 float64
f1 = 12.34
f2 = 56.78
// Original values
fmt.Println(f1, f2)
uptr = unsafe.Pointer(&f1)
atomic.SwapPointer(&uptr, unsafe.Pointer(&f2))
f1 = *(*float64)(unsafe.Pointer(uptr))
// f1 now holds the value of f2, f2 is untouched
fmt.Println(f1, f2)
交换调用(以及其他原子操作,如CAS)映射到CPU架构的指令,该指令保证了这种原子性(有关更多详细信息,请参阅https://stackoverflow.com/a/1762179/1094941)。至于为什么没有花车的装配支持,我不知道。
答案 2 :(得分:1)
为了改进Go上下文中的先前答案,我们可以使用https://golang.org/pkg/math/#Float64bits和https://golang.org/pkg/math/#Float64frombits将float64s转换为uint64s,而不直接使用不安全的软件包。
鉴于uint64,我们可以使用所有可用的原子基元。
type AtomicFloat64 uint64
func (f *AtomicFloat64) Value() float64 {
return math.Float64frombits(atomic.LoadUint64((*uint64)(f)))
}
func (f *AtomicFloat64) Add(n float64) float64 {
for {
a := atomic.LoadUint64((*uint64)(f))
b := math.Float64bits(math.Float64frombits(a) + n)
if atomic.CompareAndSwapUint64((*uint64)(f), a, b) {
return
}
}
}
答案 3 :(得分:0)
CAS(比较和交换)是最重要的原子操作,因为可以用它来模拟其他(add
,and
,or
等)。
我认为,浮点数不具有CAS功能的一个重要原因是,相等性不适用于IEEE754浮点数,其处理方式与整数类型不一样。例如,只要原来的期望值或实际值变成NaN
,您就不会知道CAS交换是否成功。请记住,将NaN
与包含NaN
的任何其他值进行比较将始终返回false。
对于原子按位和算术运算,它们对浮点的用处远不如对整数有用。