The Go Blog
Делитесь памятью, используя коммуникацию
Традиционные модели работы с потоками (как правило, используемые при написании программ на Java, C++ и Python, например) требуют от программиста общения между потоками с использованием общей памяти. Обычно структуры общих данных защищаются блокировками, и потоки конкурируют за эти блокировки для доступа к данным. В некоторых случаях это упрощается использованием потокобезопасных структур данных, таких как очередь (Queue) в Python.
Примитивы параллелизма в Go — горутины и каналы — предоставляют элегантное и отличное средство для структурирования конкурентного программного обеспечения. (Эти концепции имеют интересную историю, которая начинается с языка C. А. Р. Хоара и его Коммуникационные последовательные процессы.) Вместо того чтобы явно использовать блокировки для управления доступом к общим данным, Go поощряет использование каналов для передачи ссылок на данные между горутинами. Этот подход гарантирует, что в любой момент времени только одна горутина имеет доступ к данным. Концепция описана в документе Effective Go (обязательное чтение для каждого Go-разработчика):
Не передавайте данные через разделяемую память; вместо этого передавайте память через коммуникацию.
Рассмотрим программу, которая опрашивает список URL-адресов. В традиционной многопоточной среде структура данных могла бы выглядеть следующим образом:
<code>type Resource struct {
url string
polling bool
lastPolled int64
}
type Resources struct {
data []*Resource
lock *sync.Mutex
}
</code>
И функция Poller (множество таких функций будет работать в отдельных потоках) могла бы выглядеть примерно так:
<code>func Poller(res *Resources) {
for {
// получить ресурс, который был опрашиваем последним
// и отметить его как находящийся в процессе опроса
res.lock.Lock()
var r *Resource
for _, v := range res.data {
if v.polling {
continue
}
if r == nil || v.lastPolled < r.lastPolled {
r = v
}
}
if r != nil {
r.polling = true
}
res.lock.Unlock()
if r == nil {
continue
}
// опросить URL
// обновить данные ресурса о времени последнего опроса
res.lock.Lock()
r.polling = false
r.lastPolled = time.Nanoseconds()
res.lock.Unlock()
}
}
</code>
Эта функция занимает около страницы текста и требует дополнительных деталей, чтобы стать полной. Она даже не включает логику опроса URL (которая сама по себе состояла бы всего из нескольких строк), а также не обрабатывает ситуацию исчерпания доступных ресурсов.
Рассмотрим ту же функциональность, реализованную с использованием идиоматического подхода в Go. В этом примере Poller — это функция, которая получает ресурсы для опроса из входного канала, и отправляет их в выходной канал после завершения.
<code>type Resource string
func Poller(in, out chan *Resource) {
for r := range in {
// опросить URL
// отправить обработанный ресурс в out
out <- r
}
}
</code>
Изящная логика предыдущего примера отсутствует явно, и структура данных Resource больше не содержит служебных данных. На самом деле, остаются только важные части. Это должно дать представление о силе этих простых языковых особенностей.
В приведённых выше фрагментах кода много опущено. Для подробного обзора полной, идиоматической программы на Go, использующей эти идеи, см. Codewalk Share Memory By Communicating.
Следующая статья: Defer, Panic и Recover
Предыдущая статья: Синтаксис объявления в Go
Индекс блога