The Go Blog
Делитесь памятью, общаясь
Традиционные модели многопоточности (как правило, используемые при написании программ на Java, C++ и Python, например) требуют от программиста общения между потоками с использованием общей памяти. Обычно структуры общих данных защищаются блокировками, и потоки конкурируют за эти блокировки, чтобы получить доступ к данным. В некоторых случаях это упрощается использованием потокобезопасных структур данных, таких как очередь в 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
Индекс блога