Как стать автором
Обновить

Комментарии 4

В коде примера метод Hello объявлен на ресивере указателя Ben или Jerry. Если вместо этого объявить метод на значении Ben или Jerry, решит ли это проблему гонки данных?

Не решит, т.к. во первых проблема не в данных интерфейса, а в том, что тип и данные записываются разными операциями записи, между которыми может вклинится другая операция. И в самом интерфейсе всегда хранится ссылка на данные (uintptr). Не важно, что мы определили приемником значение, а не ссылку.

Или я не прав?

Это только полбеды. Здесь гонка сразу в трех местах: при записи в loop0 и loop1 и при чтении в цикле. Даже если мы синхронизируем изменение maker мы все равно будем иметь туже самую проблему.

Упрощенно этот пример можно было бы переписать следующим образом:

package main

import (
	"fmt"
)

type Ben struct {
	name string
}

func BenHello(b *Ben) {
	fmt.Printf("Ben says, \"Hello my name is %s\"\n", b.name)
}

type Jerry struct {
	name string
}

func JerryHello(j *Jerry) {
	fmt.Printf("Jerry says, \"Hello my name is %s\"\n", j.name)
}

func main() {
	var ben = &Ben{"Ben"}
	var jerry = &Jerry{"Jerry"}
	var maker interface{} = ben

	var loop0, loop1 func()

	loop0 = func() {
		maker = ben
		go loop1()
	}

	loop1 = func() {
		maker = jerry
		go loop0()
	}

	go loop0()

	for {
		switch m := maker.(type) { // <--- очевидно, что эта операция
		case *Jerry:
			JerryHello(m) // <--- и эта
		case *Ben:
			BenHello(m) // <--- не атомарны
		}
	}
}

Между проверкой типа maker и вызовом JerryHello/BenHello значение maker может поменяться, даже если мы защитим loop0/loop1 мьютексом. Очевидно, если мы заменим указатель значением, то ничего не поменяется и switch никуда не уйдет. Полагаю, что примерно тоже самое происходит и с интерфейсом.

Впрочем это легко проверить.
Синхронизируем только запись: https://go.dev/play/p/KJrxnbf6pB9

func main() {
	...
	var lock sync.Mutex
	...
	loop0 = func() {
		lock.Lock()
		defer lock.Unlock()
		
		maker = ben
		go loop1()
	}

	loop1 = func() {
		lock.Lock()
		defer lock.Unlock()
		
		maker = jerry
		go loop0()
	}
	...
}

Результат:

panic: I'm Ben! Not Jerry

goroutine 1 [running]:
main.(*Ben).Hello(0xc00003e730?)
	/tmp/sandbox17993843/prog.go:18 +0x59
main.main()
	/tmp/sandbox17993843/prog.go:61 +0x1cc

Не помогло. А если синхронизировать только чтение? https://go.dev/play/p/DIcbbDLZPVL

func main() {
	...
	var lock sync.Mutex
	...
	for {
		lock.Lock()
		maker.Hello()
		lock.Unlock()
	}
}

Тоже не помогло.

Последняя попытка, объединяем оба кода: https://go.dev/play/p/PLucwu_K25t
Вот только теперь работает как надо.

Кстати, в данном случае можно было бы без мьютекса обойтись.

func main() {
	var ben = &Ben{"Ben"}
	var jerry = &Jerry{"Jerry"}
	var maker IceCreamMaker = ben
	ch := make(chan IceCreamMaker, 1)
	defer close(ch)

	var loop0, loop1 func()

	loop0 = func() {
		ch <- ben
		go loop1()
	}

	loop1 = func() {
		ch <- jerry
		go loop0()
	}

	go loop0()

	for {
		select {
		case m := <-ch:
			maker = m
		default:
			maker.Hello()
		}
	}
}

А почему -race не детектит гонку данных для примера кода выше?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории