Странное поведение int внутри структуры


Предположим, у нас есть такая структура (одна из самых простых в истории):

type some struct{
    I uint32
}

И мы хотим иметь переменную такого типа и атомарно инкрементировать в цикле for (возможно, в другом goroutine, но теперь история другая). Я делаю следующее:

q := some{0}
for i := 0; i < 10; i++ {
        atomic.AddUint32(&q.I,1) // increment [1]
        fmt.Println(q.I)
}

Мы получаем то, что ожидали, пока все хорошо, но если мы объявим функцию для этого типа следующим образом:

func (sm some) Add1(){
    atomic.AddUint32(&sm.I,1)
}

И вызовите эту функцию в приведенном выше примере (строка [1]) значение не увеличивается, и мы просто получить нули. Вопрос очевиден-почему?

Это должно быть что-то основное, но так как я новичок, чтобы идти, я не понимаю этого.
2 4

2 ответа:

Спецификация Языка Программирования Go

Вызовы

В вызове функции значение функции и аргументы вычисляются в обычный порядок. После того, как они оценены, параметры вызова передаются по значению в функцию и вызываемая функция начинается исполнение. Возвращаемые параметры функции передаются по значению вернитесь к вызывающей функции, когда функция вернется.

Приемник sm some является передается по значению в метод, и копия отбрасывается при возврате из метода. Используйте приемник указателей.

Например,

package main

import (
    "fmt"
    "sync/atomic"
)

type some struct {
    I uint32
}

func (sm *some) Add1() {
    atomic.AddUint32(&sm.I, 1)
}

func main() {
    var s some
    s.Add1()
    fmt.Println(s)
}

Вывод:

{1}

Перейти Часто задаваемые вопросы (FAQ)

Когда параметры функции передаются по значению?

Как и во всех языках семейства C, все в Go передается через значение. То есть функция всегда получает копию вещи, являющейся прошел, как будто было задание заявление о присвоении значения к параметру. Например, передача значения int в функцию создает копию int, а передача значения указателя создает копию указатель, но не данные, на которые он указывает.

Должен ли я определять методы на значениях или указателях?

func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

Для программистов, непривычных к указателям, различие между эти два примера могут сбить с толку, но на самом деле ситуация такова. очень просто. При определении метода для типа приемник (ы в выше примеры) ведет себя точно так же, как если бы это был аргумент к метод. Определить ли приемник как значение или как указатель-это таким образом, тот же вопрос, что и вопрос о том, должен ли аргумент функции быть значение или указатель. Есть несколько соображений.

Во-первых, и это самое важное, нужно ли методу изменять приемник? Если это так, то получатель должен быть указателем. (Срезы и карты действуют как ссылки, поэтому их история немного более тонкая, но для например, чтобы изменить длину среза в методе, получатель должен все равно будь указателем.) В приведенных выше примерах, если pointerMethod изменяет поля s, вызывающий увидит эти изменения, но valueMethod является вызывается с копией аргумента вызывающего (это определение передавая значение), так что изменения, которые он делает, будут невидимы для вызывающего объекта.

Кстати, приемники указателей идентичны ситуации в Java, хотя в Java указатели скрыты под одеялом-это Гоу. приемники значения, которые необычны.

Во-вторых, это рассмотрение эффективности. Если ресивер большой, большая структура, например, это будет гораздо дешевле использовать указатель приемник.

Далее идет последовательность. Если некоторые из методов типа должны иметь приемники указателей, остальные тоже должны, поэтому набор методов согласовано независимо от того, как используется тип. В разделе наборы методов для деталей.

Для таких типов, как как основные типы, срезы и малые структуры, значение приемник очень дешев, так что если семантика метода не требует указатель, приемник значений эффективен и понятен.

Ваша функция должна получить указатель на значение, которое будет увеличено, таким образом, вы не передаете копию структуры и на следующей итерации I может быть увеличен.

package main

import (
"sync/atomic"
"fmt"
)

type some struct{
    I uint32
}

func main() {
q := &some{0}
for i := 0; i < 10; i++ {
        q.Add1()
        fmt.Println(q.I)
}
}

func (sm *some) Add1(){
    atomic.AddUint32(&sm.I,1)
}