2014-01-08 3 views
8

Я новичок в языке Go, поэтому, пожалуйста, извините меня, если мой вопрос очень прост. Я написал очень простой код:Как goroutines ведут себя на многоядерном процессоре

func main(){ 
    var count int // Default 0 

    cptr := &count 

    go incr(cptr) 

    time.Sleep(100) 

    fmt.Println(*cptr) 
} 

// Increments the value of count through pointer var 
func incr(cptr *int) { 
    for i := 0; i < 1000; i++ { 
      go func() { 
        fmt.Println(*cptr) 
        *cptr = *cptr + 1 
      }() 
     } 
    } 

Значение счетчика должно увеличиваться на единицу количеством циклов цикла Рассмотрим случаи:

Loop работает 100 раз -> значение count равно 100 (это верно, поскольку цикл работает 100 раз).

Loop работает на> 510 раз -> Значение счетчика либо 508 или 510. Это происходит, даже если это 100000.

Я бегу это на процессорной машине 8 сердечника.

ответ

8

Прежде всего: до Go 1.5 он работает на одном процессоре, используя только несколько потоков для блокировки системных вызовов. Если вы не сообщите во время работы использовать больше процессоров, используя GOMAXPROCS.

Начиная с Go 1.5 GOMAXPROCS устанавливается на число CPUS. См. 6, 7.

Кроме того, операция *cptr = *cptr + 1 не гарантируется быть атомарной. Если вы внимательно посмотрите, его можно разделить на 3 операции: получить старое значение с помощью указателя разыменования, значение приращения, сохранить значение в адрес указателя.

Тот факт, что вы получаете 508/510, вызван некоторой магией во время выполнения и не определен так, чтобы оставаться таким образом. Более подробную информацию о поведении операций с параллелизмом можно найти в Go memory model.
Вы, вероятно, получаете правильные значения для < 510 запустили goroutines, потому что любое число ниже этого не прерывается (пока).

Как правило, то, что вы пытаетесь сделать, не рекомендуется ни на одном языке, ни в способе «Перейти» для параллелизма. Очень хороший пример использования каналов для синхронизации этого кода ходьба: Share Memory By Communicating (а не общение по обмену памятью)

Here is a little example to show you what I mean: использовать канал с буфером 1 для сохранения текущего номера, извлекать его из канала, когда вы нуждайтесь в ней, измените ее по своему усмотрению, затем верните ее для использования другими.

+0

Ссылка на модель памяти на самом деле довольно приятная и очищает многие вещи. – Nerve

9

Ваш код исправный: вы записываете в одно и то же место памяти из разных несинхронизированных goroutines без какой-либо блокировки. Результат в основном не определен. Вы должны либо a) убедиться, что все goroutine записываются друг за другом красивым, упорядоченным способом или b) защищают каждую запись, например, e mutex или c) использовать атомные операции.

Если вы пишете такой код: всегда используйте его под детектором гонки, как $ go run -race main.go, и исправьте все гонки.

+0

Любая причина, почему это зависит от входа? При значениях менее 510 он всегда печатает правильно o/p. – Nerve

+1

Нет причин, просто шанс. – Volker

+0

Как говорит Фолькер, причина, по которой это правильно с небольшими значениями, в основном является случайным шансом, что планировщик вызвал ожидаемое поведение. Причина, по которой он терпит неудачу с более чем 510, вероятно, из-за того, что вы спите за 100 нс, затем прекратите работу, поэтому значения 508/510 - это то, что было у вас, когда основная программа просыпается и уходит. Это будет варьироваться в зависимости от нагрузки системы и других факторов. – zstewart

1

Хорошей альтернативой использованию каналов в этом случае может быть пакет sync/atomic, который содержит специальные функции для атомарного увеличения/уменьшения чисел.

-1

Все это подходило ко мне, noobie here. Но я нашел лучший способ http://play.golang.org/p/OcMsuUpv2g

Я использую пакет sync для решения этой проблемы и жду, пока все goroutines не закончатся, без сна или канала.

И не забудьте взглянуть на этот удивительный пост http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

+0

a) Ссылка только ответы не подходят для [так]. б) Использование канала таким образом является излишним для простого счетчика; если это просто счетчик 'sync.Atomic', это намного сложнее. –

+0

Эй, спасибо за подсказку, не могли бы вы переписать лучший способ? –

Смежные вопросы