Объясните: не общайтесь, разделяя память; делиться памятью, общаясь

Интересно, какое самое простое объяснение этой знаменитой цитаты:

Не общайтесь, разделяя память; делитесь памятью, общаясь. (Р. Пайк)

В модели памяти Go я могу прочитать следующее:

Отправка по каналу происходит до завершения соответствующего приема от этого канала. (Спецификация Голанга)

Существует также специальная статья о golang, объясняющая цитату. И ключевой вклад - это рабочий пример, также написанный Эндрю Дж.

Хорошо. Иногда слишком много разговоров ... Я извлек из цитаты из спецификации памяти, а также посмотрев на рабочий пример:

После того, как goroutine1 отправляет (что угодно) в goroutine2 через канал, все изменения (где угодно в памяти), сделанные goroutine1, должны быть видны goroutine2 после того, как они были получены через тот же канал. (Лемма Голанга, написанная мной :)

Поэтому я беру приземленное объяснение известной цитаты:

Чтобы синхронизировать доступ к памяти между двумя горутинами, вам не нужно отправлять эту память по каналу. Достаточно хорошо принимать с канала (даже ничего). Вы увидите любые изменения, которые были записаны (где угодно) при отправке горутины (на канал) во время ее отправки. (Конечно, при условии, что никакие другие горутины не записывают в ту же самую память.) Обновление (2) 8-26-2017

На самом деле у меня два вопроса:

1) Верно ли мое заключение?

2) Помогает ли мое объяснение?

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

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

Чтобы лучше понять объем моего вопроса, предположим, что горутины могут обращаться к любой структуре памяти - не только к примитивным - и она может быть большой, это может быть строка, карта, массив, что угодно.


person honzajde    schedule 03.04.2016    source источник


Ответы (7)


По сути, да. Любые значения, присвоенные переменным перед отправкой канала, могут наблюдаться после чтения канала, поскольку операция канала накладывает ограничение на порядок. Но важно помнить и о другой части уравнения: если вы хотите гарантировать, что эти значения соблюдаются, вы должны убедиться, что никто другой не может писать в эти переменные в промежутке между записью и записью. читать. Очевидно, что использование блокировок возможно, но в то же время бессмысленно, потому что, если вы уже комбинируете блокировки и межпотоковую модификацию памяти, какую выгоду вы получаете от каналов? Вы можете передать что-то столь же простое, как логическое значение, например токен, разрешающий монопольный доступ к глобальным данным, и это будет на 100% правильным с точки зрения гарантий модели памяти (при условии, что ваш код не содержит ошибок), < em> это, вероятно, просто плохой дизайн, потому что вы будете делать все неявно и на расстоянии без уважительной причины; явная передача данных обычно будет более ясной и менее подверженной ошибкам.

person hobbs    schedule 03.04.2016
comment
Пока что это касается большей части моего вопроса. Спасибо. Тем не менее, все еще ждем, когда Роб П. скажет свою роль :) - person honzajde; 04.04.2016

Эта известная цитата может немного сбить с толку, если ее воспринимать слишком буквально. Давайте разберемся с базовыми компонентами и определим их должным образом:

Don't communicate by sharing memory; share memory by communicating
      ---- 1 ----    ------ 2 -----  ---- 3 -----    ----- 4 -----
  1. Это означает, что разные потоки выполнения будут проинформированы об изменении состояния других потоков путем чтения памяти, которая будет изменена где-то еще. Прекрасным примером этого (хотя для процессов, а не потоков) является API общей памяти POSIX: http://man7.org/linux/man-pages/man7/shm_overview.7.html. Этот метод требует правильной синхронизации, потому что гонки данных могут возникать довольно легко.
  2. Здесь это означает, что действительно есть часть памяти, физическая или виртуальная, которую можно изменять из нескольких потоков, а также читать из них. Нет явного понятия владения, пространство памяти одинаково доступно для всех потоков.
  3. Это совсем другое. В Go возможно совместное использование памяти, как указано выше, и гонки данных могут происходить довольно легко, поэтому на самом деле это означает изменение переменной в горутине, будь то простое значение, такое как int, или сложная структура данных, такая как карта, и передать право собственности, отправив значение или указатель на значение другой горутине через механизм канала. Так что в идеале общего пространства нет, каждая горутина видит только ту часть памяти, которой она владеет.
  4. Коммуникация здесь просто означает, что канал, который является всего лишь очередью, позволяет одной горутине читать из него и, таким образом, получать уведомление о владении новой частью памяти, в то время как другой отправляет ее и соглашается потерять владение. Это простой шаблон передачи сообщений.

В заключение то, что означает цитата, можно резюмировать следующим образом:

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

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

person SirDarius    schedule 03.04.2016
comment
Спасибо за ваш вклад @SirDarius. Пока что этот вопрос, который я задал, вызывает у меня массу удовольствия :) Надеюсь, что и всем остальным. А вот ответ на мои вопросы 1) Нет 2) Нет? - person honzajde; 04.04.2016
comment
Я бы сказал, да и да, хотя я не уверен, что формулировка вашего вывода звучит идиоматично для разработчиков Go, если вы понимаете, что я имею в виду. По сути, отсутствует понятие перехода права собственности. - person SirDarius; 04.04.2016
comment
Эта часть вашего ответа действительно вводит в заблуждение: ... и отдать право собственности, отправив значение или указатель на значение другой горутине через механизм канала - в go нет концепции владения, и на самом деле предположение, что это приводит к состояние гонки, если отправка горутины снова изменяет данные. - person honzajde; 03.10.2017
comment
Поскольку фактически отсутствует понятие собственности, это действительно связано с лучшими практиками, о которых, как вы заявили, ваш вопрос не идет. Тем не менее, я бы сказал, что совместное использование памяти путем общения является руководством для передовых практик, в частности, не используйте повторно переменные, которые вы отправили через канал. - person SirDarius; 03.10.2017

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

Самый простой способ добиться этого - разделить ссылку на память по каналам. Отправив ссылку по каналу, вы забываете. Таким образом, только процедура, которая будет использовать этот канал, будет иметь к нему доступ.

person fabrizioM    schedule 03.04.2016
comment
Да, суть была в совершенно другом - как вы описываете во втором абзаце. Но если я ошибаюсь, тогда где я ошибаюсь ?. - person honzajde; 04.04.2016

В этом есть два предложения; для более полного понимания их нужно сначала рассматривать отдельно, а затем объединять вместе. Итак: Don't communicate by sharing memory; означает, что разные потоки не должны взаимодействовать друг с другом, соблюдая строгие и подверженные ошибкам политики видимости и синхронизации памяти, такие как барьер памяти и т. д. (Обратите внимание, что это можно сделать, но вскоре это может стать сложным и содержать много ошибок с гонками данных). Поэтому избегайте соблюдения ручных программных конструкций видимости, которые в основном достигаются посредством надлежащей синхронизации в языках программирования, таких как Java.

share memory by communicating. означает, что если один поток внес какие-либо изменения (записи) в область памяти, он должен передать то же самое (область памяти) потоку, заинтересованному в той же области памяти; обратите внимание, это уже ограничило объем памяти только двумя потоками.

Теперь прочтите два вышеупомянутых абзаца в сочетании с моделью памяти golang, которая гласит: A send on a channel happens before the corresponding receive from that channel completes. Отношение происходит до того, как подтверждается, что запись первой горутиной будет видна второй горутине, получающей ссылку на память на другом конце канала!

person nawazish-stackoverflow    schedule 05.10.2016

Позвольте мне сказать здесь просто и по существу.

Не общайтесь, делясь воспоминаниями.

Это похоже на то, когда вы общаетесь с использованием потоков, например, вы должны использовать переменные или мьютексы для блокировки памяти, чтобы кто-то не мог читать и писать в ней до тех пор, пока связь не будет завершена.

Делитесь памятью, общаясь

На ходу значения подпрограмм перемещаются по каналам, а не блокируют память, отправитель уведомляет получателя о получении из этого канала и, следовательно, он разделяет память, связываясь с получателем, чтобы получить из канала.

person Himanshu    schedule 11.06.2017

1) Верен ли мой вывод?

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

Проблема с вашим описанием заключается в том, что вы явно не определили порядок случайности. Я думаю, что вы имеете в виду приказ. Если вы имеете в виду, что «операции в goroutine a, которые происходят до того, как goroutine a работает с определенной точкой синхронизации, будут видны goroutine b после того, как goroutine b также обнаружит ту же точку синхронизации» - даже здесь, «точка синхронизации» плохо определен - хотя я надеюсь, вы это понимаете. Такая точка может быть определена случайно, как и в спецификации.

2) Помогает ли мое объяснение?

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

  • Вы не определили строго, что «отправка», о которой вы говорите, является точкой синхронизации. Если вы имеете в виду отправку по небуферизованному каналу, тогда да, это создаст общую точку синхронизации, которая по спецификации вводит строгий порядок случайности.
  • Хотя, если предположить, что вышеизложенное верно, вы описали точку синхронизации, это касается только одной стороны исходной рекомендации. Первоначальный совет включает в себя концепцию «передачи права собственности», и это связано не столько с созданием точек синхронизации или случайностей, сколько с долгосрочным обслуживанием кода, который полагается на потенциально разделяемую память. Идея состоит в том, что вместо сохранения доступа к некоторому сегменту памяти в двух местах и ​​создания отдельных общих точек синхронизации (например, мьютексов) можно вместо этого передать, возможно, единственную ссылку на объект от одного владельца к другому. Такое проектирование программного обеспечения предотвращает случайное изменение за пределами точек синхронизации, что часто наблюдается в программном обеспечении с частичным использованием мьютексов и широким использованием разделяемой памяти.

Мьютексы или «явные точки синхронизации» прямо противоположны совету - они представляют собой разделяемую часть памяти, которая используется для передачи точки синхронизации «общение путем совместного использования памяти», тогда как, даже если у них есть мьютексы глубоко под капотом, каналы являются абстрактным механизмом для передачи владения объектом (отправленное значение) от одной горутины (отправителя) к другой горутине (получателю). Дело в том, игнорируя то, как реализованы каналы, что пользователь имеет разделяемую память (значение), передавая ее от одного владельца (горутина а) другому (горутина б). Если вы используете канал для отправки несвязанных данных для создания точки синхронизации, вы, по сути, используете его как мьютекс, и это ближе к общению посредством совместного использования памяти (фокус на канале), а не обмена посредством общения (сфокусируйтесь на Значение).

Надеюсь, это поможет.

person raggi    schedule 03.04.2016
comment
Спасибо, что указали на буферизованные каналы! Я обновил свой вопрос, так что очевидно, что я изначально предполагал небуферизованные каналы. - person honzajde; 04.04.2016

  1. общайтесь, разделяя память: это традиционный способ борьбы с многопоточностью. Если некоторые данные совместно используются двумя потоками, чтобы они, например, не пытались записать оба одновременно, необходимо использовать некоторые примитивы синхронизации. Это, как известно, сложно отлаживать. Интересно иметь в виду, что при разделении времени процессор выделяет временной интервал (например, 100 мсек) одному потоку, а затем переключает контекст и выделяет следующий временной интервал другому потоку. Переключение происходит, когда операционная система не знает, что делает код, выполняемый потоками. Если операции не атомарны, они могут быть прерваны операционной системой на любом этапе.

  2. совместное использование памяти путем обмена данными: обычно это происходит при использовании модели субъектов, в которой каждый субъект отображается на ядро ​​процессора. Актеры обмениваются сообщениями, используя систему почтовых ящиков, где получатель получает копию сообщения. Нет общих данных, поэтому нет необходимости в механизмах синхронизации, таких как мьютексы. Другого названия для этой архитектуры ничего не поделено.

person Ludovic Aubert    schedule 01.02.2021