Большинство языков предоставляют функции для атомарных int
операций (добавление, сравнение и замена и т. д.).
Почему не для типов с плавающей запятой?
Большинство языков предоставляют функции для атомарных int
операций (добавление, сравнение и замена и т. д.).
Почему не для типов с плавающей запятой?
Давайте подумаем об атомарности с плавающей запятой с точки зрения проектирования ОС/железа...
Атомары существуют, потому что они нужны для синхронизации. Что включает в себя большая часть синхронизации? Дескрипторы, флаги, мьютексы, спин-блокировки — вещи, фактическое значение которых бессмысленно, пока оно согласовано для каждого пользователя и различается между пользователями. Даже для чего-то вроде семафора, где значение имеет большее значение, речь идет о счете, а не о измерении, поэтому 32 бита стоят 32 бита, что бы мы ни представляли.
Во-вторых, технические проблемы. Почти все, что мы можем запрограммировать, выполняет целочисленные операции. Не так с плавающей запятой - когда операции FP эмулируются библиотекой C, эти атомы будут между трудными и невозможными для реализации. Даже на аппаратном уровне операции FP обычно будут медленнее, чем целочисленные, а кому нужны медленные блокировки? Конструкция самого FPU может даже затруднить реализацию атомарных операций, например. если он висит на интерфейсе сопроцессора без прямого доступа к шине памяти.
Секунда с половиной, если мы хотим float
, наверняка мы также хотим double
? Но double
часто имеет проблему, заключающуюся в том, что он больше, чем машинное слово, что исключает атомарность даже загрузки и сохранения на многих архитектурах.
В-третьих, когда дело доходит до таких вещей, как атомарность, архитекторы ЦП, как правило, реализуют то, что требуют системные дизайнеры и разработчики ОС, а разработчики ОС вообще не очень любят числа с плавающей запятой — дурацкие дополнительные регистры для сохранения, замедляющие переключение контекста... Дополнительные инструкции/функции в аппаратном обеспечении стоят мощности и сложности, и если клиенты не хотят этого...
Короче говоря, вариантов использования недостаточно, поэтому нет аппаратной поддержки, поэтому нет языковой поддержки. Конечно, на некоторых архитектурах вы можете создавать собственные атомарные числа, и я представьте себе, что вычислительные ресурсы графического процессора могут иметь больший спрос на синхронизацию на оборудовании, в основном работающем с плавающей запятой, так что кто знает, останется ли так?
Чтобы улучшить предыдущие ответы в контексте Go, мы можем использовать https://golang.org/pkg/math/#Float64bits и https://golang.org/pkg/math/#Float64frombits для преобразования float64 в uint64 и обратно без прямого использования пакета unsafe.
Получив 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
}
}
}
a := atomic.LoadUint64
, потому что это может измениться до следующего оператора if
. См. golang.org/src/sync/atomic/64bit_arm. .go?s=508:615#L27
- person Sergey Kamardin; 12.07.2017
Ах, исходный вопрос был помечен как 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) сопоставляется с архитектурой ЦП. инструкция, которая гарантирует эту атомарность (подробнее см. https://stackoverflow.com/a/1762179/1094941). Что касается того, почему нет поддержки сборки для поплавков, я не знаю.
CAS (compare-and-swap) — самая важная атомарная операция, поскольку другие операции (add
, and
, or
и т. д.) можно моделировать в ее терминах.
Я думаю, что важная причина, по которой числа с плавающей запятой не имеют функциональности CAS, заключается в том, что равенство не работает с числами с плавающей запятой IEEE754 так же, как с целочисленными типами. Например, вы не будете знать, был ли своп успешным с CAS, если старое ожидаемое или фактическое значение оказалось NaN
. Помните, что сравнение NaN
с любым другим значением, включая NaN
, всегда возвращает false.
Что касается атомарных побитовых и арифметических операций, они просто намного менее полезны для операций с плавающей запятой, чем для целых чисел.