Golang: для чего используется атомарное чтение?

Здесь у нас есть пример, предоставленный Go by Example, чтобы объяснить атомный пакет.

https://gobyexample.com/atomic-counters

package main

import "fmt"
import "time"
import "sync/atomic"

func main() {

    var ops uint64

    for i := 0; i < 50; i++ {
        go func() {
            for {
                atomic.AddUint64(&ops, 1)

                time.Sleep(time.Millisecond)
            }
        }()
    }

    time.Sleep(time.Second)

    opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
    fmt.Println("ops:", opsFinal)
}

Для atomic.AddUnit64 это просто понять.

Вопрос 1

Что касается операции read, почему необходимо использовать atomic.LoadUnit, а не читать этот счетчик напрямую?

Вопрос 2

Могу ли я заменить последние две строки следующими строками?

До

    opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
    fmt.Println("ops:", opsFinal)

После

    opsFinal := ops
    fmt.Println("ops:", opsFinal)

Вопрос3

Нас беспокоит этот сценарий?

  1. CPU загружает данные из памяти
  2. ЦП манипулирует данными
  3. Запишите данные обратно в память. Несмотря на то, что этот шаг быстрый, но он все равно требует времени.

Когда ЦП выполняет шаг 3, другая горутина может считать неполные и грязные данные из памяти. Так что использование atomic.LoadUint64 может избежать такой проблемы?

Ссылка

Являются ли операции чтения и записи для uint8 в golang атомарными?

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
6
0
2 084
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Необходимо использовать atomic.LoadUint64, потому что нет гарантии, что оператор := выполняет атомарное чтение.

В качестве примера рассмотрим теоретический случай, когда atomic.AddUint64 реализован следующим образом:

  1. Возьми замок.
  2. Прочитайте младшие 32 бита.
  3. Прочитайте верхние 32 бита.
  4. Добавьте число к младшим 32 битам.
  5. Добавьте выполнение первой операции к старшим 32 битам.
  6. Напишите младшие 32 бита.
  7. Напишите старшие 32 бита.
  8. Освободить замок.

Если вы не используете atomic.LoadUint64, возможно, вы читаете промежуточный результат между шагами 6 и 7.

На некоторых платформах (например, более старые процессоры ARM без встроенной поддержки 64-битных целочисленных операций) это вполне может быть реализовано описанным выше способом.

То же самое относится и к целым числам/указателям других размеров. Точное поведение будет зависеть от реализации пакета atomic и архитектуры ЦП/памяти, на которой работает программа.

Другие вопросы по теме