Pull to refresh
15
0
Сергей Пуликов@scriptuoz

User

Send message

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

Вы путаете понятия. Есть спецификация языка, вы можете с ней ознакомиться по ссылке и в ней вы не найдете про "ссылку под капотом" потому что этого нет. Далее есть реализация, которая своим поведением полностью соответствует спецификации. А вот далее произошло то что вы не разобрались в той реализации которую увидели в искодниках и решили, что есть какая то магия. Но магии нет, ссылка там по вполне очевидным причинам и эти причины не "под капотом передается указатель". В горутину читатель, либо в буфер канала передается не ссылка а копия значения, и это можно увидеть в исходниках если заглянуть чуть дальше на несколько строчек (см. memmove в функциях "отправки"). В chansend передается по указателю потому что мы должны получить значение переменной в момент "отправки значения" а не в момент вызова функции chansend, момент отправки происходит когда мы кладем значение отправляемое в канал либо на стек горутины-читателя, либо в буфер канала. И вот если бы мы в chansend передавали не ссылку а значение, то при блокировке горутины, например канал не готов принять (нет читателя или буфер заполнен) мы должны остановить выполнение и к моменту когда горутина разблокируется (появится читатель или место в буфере) переменная может быть изменена другими горутинами, но мы от нее ужа отвязались так как взяли значение а не ссылку и как результат мы положим на стек горутины-читателя или в буфер канала старое значение. И это будет нарушением спецификации языка, то есть то самое явление когда реализация не соответствует спецификации. Но, нам повезло, в go team достаточно хорошие инженеры, чтобы не допустить такой ошибки.

> в ваших размышлениях отсутствует какое-либо логическое объяснение того почему переменная либо "замыкается" либо нет в зависимости от того прибавляется к ней ноль или нет.

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

ну и по поводу вашего примера, я если честно даже не знаю как еще показать простейшее элементарное поведение

ch <- i + 0
это 2 операции
сначала выполняется i + 0
значение кладется на стек (можно по другому сказать создается временная переменная в которой хранится значение операции i + 0)
далее выполняется ch <- (та самая временная переменная) И уже в этот момент тут нет никакой переменной i, тут есть канал оператор отправки в канал и временная переменная в которой содержится значение предыдущей операции. Но продолжим и в момент когда эта операция может быть исполнена (напомню это либо есть читатель, либо есть место в буфере) передается значение той самой временной переменной и тут нет никакой переменной i здесь есть "временная переменная" результат предыдущей операции (i + 0)

в ситуации ch <- i
есть только одна операция, передача в канал, и когда эта операция может быть выполнена (а это напомню либо есть читатель, либо есть место в буфере) берется значение из переменной i

Далее по поводу дизассемблированного кода, это кстати возможно даже лучше будет моих объяснений. Вот здесь https://godbolt.org/z/5dKKrnGxW предлагяю поиграться с разными значениями, попробуйте разные варианты
ch <- i
ch <- i + 0
ch <- i + 1
ch <- i + 2

вы увидете что во всех этих вариантах код разный и даже варианты +0 +1 +2 различаются между собой. Но также вы увидите то о чем собственно я и говорю, что в случаях + 0, + 1, + 2 появляется дополнительная операция и результат этой операции используется уже в отправке в канал, а при отсутствии арифметической операции работа идет непосредственно с переменной.

ch <- i
PCDATA $1, $0

CALL runtime.chansend1(SB)

ch <- i + 0

MOVQ (CX), CX

MOVQ CX, command-line-arguments..autotmp_0+16(SP)

LEAQ command-line-arguments..autotmp_0+16(SP), BX

PCDATA $1, $0

CALL runtime.chansend1(SB)

ch <- i + 1

MOVQ (CX), CX

INCQ CX

MOVQ CX, command-line-arguments..autotmp_0+16(SP)

LEAQ command-line-arguments..autotmp_0+16(SP), BX

PCDATA $1, $0

CALL runtime.chansend1(SB)

ch <- i + 2

MOVQ (CX), CX

ADDQ $2, CX

MOVQ CX, command-line-arguments..autotmp_0+16(SP)

LEAQ command-line-arguments..autotmp_0+16(SP), BX

PCDATA $1, $0

CALL runtime.chansend1(SB)

но лучше сами попробуйте, разные варианты, если дружите с ассемблером

> какая функция "замкнула" переменную и "изменяет" её? вы о чём? внешняя её изменяет но не замыкает (т.к. переменная глобальная) а внутренняя замыкает но не изменяет

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

package main

import "fmt"

func main() {
	example()
}

func example() {
	var i = 0
	f := func() int {
		i = 15
		return i
	}

	fmt.Println(i*i + 15)  // 15
	fmt.Println(i*i + f()) // 240
}

переменная i здесь не глобальная, а локальная для функции example, функция которую мы присвоили переменной f замкнула переменую i и... ну а далее надеюсь вы помните, и возможно даже увидите сходство примеров, что здесь f() выполняется до того как будет выполнена арифметическая операция, так как если бы было иначе, то первым должно было бы выполниться умножение в соответствии с приоритетом над +
но на всякий случай допишу - сходство примера здесь в том, что казалось бы на одной строчке кода, есть разные две операции имеющие разный приоритет выполнения, в этом примере функция выполнится до того как начнет выполняться арифметическая операция `
i * i + (значение временной переменной хранящее значение функции f)` так же и в вашем примере ch <- i + 0 есть две совершенно разные операции, с разным приоритетом выполнения и арифметическая операция i + 0 будет выполнена первой и ее значение будет "записано во временную переменную" (сохранено на стек) и вот когда дело дойдет до кода `ch <- ...` то здесь уже нет переменной i здесь есть "временная переменная хранящая полученное значение операции которая уже завершилась" (значение хранящееся на стеке)

Честно говоря если после такой попытки разжевать все до элементарных вещей вы по прежнему останетесь при своем, то я умываю руки :)

Откуда информация про "под капотом передается указатель"?
Нет в спецификации такого
По ссылке написано:
"The channel expression must be of channel type, the channel direction must permit send operations, and the type of the value to be sent must be assignable to the channel's element type."
То есть если переводить на текущий пример,то:
слева должна быть переменная типа chan<- T или chan T а справа должны быть некие данные с типом T
то есть в текущем примере слева chan int справа должно быть что угодно с типом int это может быть переменная или не переменная
Далее i + 0 это не тип int это выражение, арифметическая операция, результатом который будет значение int, но само по себе выражение не имеет тип int, как если бы мы определил функцию sum(ia, b int) int и вызывали ее вместо i + 0 то функция не имеет тип int она имеет тип func(int, int) int и результатом вызова этой функции будет значение с типом int
и дальше Both the channel and the value expression are evaluated before communication begins. то есть любые выражения должны быть приведены к нужным типам, поэтому i + 0 вычисляется еще до того как вообще началось какая либо история с каналом, и результат этого вычисления помещается на стек во временную локальную переменную. Дальше, у нас слева chan int справа результат арифм. операции сохраненный в локальной переменной и имеющий тип int и вот теперь все готово к тому чтобы communication. begins. Но, мы не можем выполнить передачу, потому что канал еще не готов, нет читателя, либо буфер заполнен, поэтому горутина блокируется не выполнив эту операцию. И вот когда появился читатель либо освободился буфер, горутина разблокируется и операция осуществляется и в этот момент в буфер канала (для буферизированных) или на стек горутины (для небуферизированных) помещается копия значения передаваемого в канал.
Для случае когда у нас нет сложение переменной i + 0 все обстоит ровно по такому же принципу как описано в спецификации.
В этот момент мы имеет слева chan int а справа мы имеем переменную i типа int то есть все готово для того чтобы communication begins. Но, канал не. готов потому что нет читателя, либо буфер заполнен, поэтому горутина блокируется не выполнив эту операцию.
Далее когда произошло чтение из канала и освободилось место в буфере, горутина разблокируется и операция выполняется, то есть в этот момент берется значение переменной i и копируется в буфер (для буферизированных) или на стек горутины читающей (для небуферизированных)

Никаких упоминаний о "под капотом передается указатель" нет в спецификации

Вот еще небольшой пример на схожую тему

    var i = 0
	f := func() int {
		i = 15
		return i
	}

	fmt.Println(i*i + 15) // 15
	fmt.Println(i*i + f()) // 240

казалось бы сначала выполняется умножение и только потом сложение, но мы можем использовать в выражении только то что подходит по типу, а f() не подходит по типу, так как это не int это функция результатом выполнения который будет значение с типом int, поэтому прежде, чем приступить к разбору арифметической операции и ее вычислению, мы выполняем функцию, которая замкнула переменную i и изменяет ее внутри тела, поэтому когда дело дойдет до самой арифметической операции, то значение переменной i уже изменилось
точно также в примере из статьи, когда дело доходит до `ch <-` там уже нет переменной i там есть результат выражения i + 0 сохраненный в локальную переменную, и поэтому никакое дальнейшее изменение i не влияет на то, что уйдет в канал

для облегчения понимания, можно заменить i + 0 на get(i) где func get(i int) int { return i }
и в обоих случаях и get(i) иi + 0 это отдельные операции, которые выполняются до того, как дошло дело до ch <-
и в обоих этих случаях значение переменной i было вычислено до того как дошло до ch <-

Так нет же, одинаково оно вычисляется
просто при ch <- i + 0 здесь не одна операция отправки в канал, а две операции: ch <- и i + 0, и первая из них это сложение i + 0 и она вычисляется первой потому что исполнение в этом сулчае справа налево, до того как перейти к следующей, и для вычисления этой операции необходимо получить значение i и сложить с 0 и получившееся значение передать в следующую операцию, поэтому когда дело доходит до ch <- то здесь уже не ch <- i а ch <- (значение получившееся от предыдущей операции сложения)
в случае когда у нас нет сложения, то у нас одна операция

все согласно спецификации языка, нет никаких странностей

А в чем странность поведения?

Отправка в канал происходит не в момент инструкции <- а в момент когда есть либо читатель либо буфер.

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

Упс, не обратил внимания, что это перевод, а не ваш текст. В общем по поводу критики текста это не к вам, а к Dr Gideon Greenspan, не уверен правда, что он сюда заглядвает :)
Как-то вы не расскрыли свой посыл, как мне кажется.

Взаимодействие с внешними сервисами у вас в тексте так и не расскрыто почему это невозможно?

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

То есть некто X выпускает смартконтракт, который в зависимости от наступления условия Y зачислит условно на ваш баланс Z% от некоторой суммы. Проверку наступления условий смартконтракт осуществляет обращаясь к оракулу, можно даже не одному, а нескольким, разным, и условие считается наступившим, только в случае если все оракулы ответили одинаково.

Есть ли в этом сценарии потеря децентрализаиции и появляются ли некоторые доверенные участники? Да. Но сам реестр продолжает быть таким каким он был, без потери своих свойств. То есть у нас просто есть смартконтракт, который по сути является программным кодом и представляет из себя аналог обычного бумажного контракта, только за его исполнением следит программный код. В этом и есть основная суть смартконтрактов. А вы как потребитель выбираете чей смартконтракт вам интересен и на чьи условия вы готовы согласиться.

В одном случае у вас есть некий централиованный участник, которому все верят, пусть это будет страховая компания и всякий раз вам придется бадаться именно с этим участником и его трактковками, то есть мы имеем того самого посредника, от которых нам предлагает избавиться вся эта движуха вокруг блокчейн технологий. Заменить центрального участника крайне сложно.

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

Оракулы имеют свою комиссию и поэтому предоставление некорректных данных поставит крест на его существовании, так как факт предоставления таких данных будет отражен в том самом распределенном реестре.

То есть в итоге смартконтракты служат для уменьшения посредников и повышения прозрачности.

Ну и раз уж я тут засветился, то хочу высказаться по поводу Wirex так как являюсь пожалуй одним из первых его пользователей. Это ведь ваш сервис, не так ли?
Ребята реализация на крайне низком уровне, и ладно бы черт с ним с глюками, но ваш подход зажать деньги пользователя объясняя это какими-то странными попытками системы застраховаться от скачков стоимости транзакции и при этом зажимаемая вами сумма является фактически не выводимой это уже очень странно. В моем понимании это эквивалентно краже средств пользователя, хотя конечно де-юре кражей не является. И понятно, что сумма крайне незначительная для отдельно взятого пользователя, но если с каждого по чуть-чуть то общая сумма выходит очень даже внушительной.
И вот мое мнение, что вся эта движуха с блокчейн технологиями вообще и смартконтрактами в частности как раз и стремится к избавлению от таких недобросовестных посредников как вы.

Все это мое оценочное суждение :)
На счет запросов снаружи не совсем понял суть проблемы. Если имеется в виду как организовано общение с браузером клиента, то это примерно так: есть приложение SPA на Reactjs, которое общается с нашим API, которое доступно снаружи, API в свою очередь посылает события в диспетчер и точно также получает события из диспетчера по результатам, которых меняет свое состояние. Сейчас идем к тому, что API будет получая определенные события, отправлять сообщения на клиент посредством SSE.

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

Дефолтного ТЗ нет, мы стараемся не пользоваться шаблонами в таких вопросах как постановка задач, каждый раз это написание с нуля и продумывание деталей, зато с каждым разом мы открываем все новые и новые детали =)
Диспетчер от сервиса должен дождаться только подтверждения, что сообщение получено, задерживать диспетчер не хорошо, у него работы много =)
Да, есть блокирующиеся вызовы, это обращения напрямую от сервиса к сервису, такие у нас тоже есть, но их очень мало, как правило это получение каких-то данных, которые есть в другом сервисе. Мы иногда думаем над тем, чтобы заменить и такие вызовы, чем то вроде:
сервис А кидает сообщение в диспетчер что ему нужны данные Y (событие: «нужны данные Y»)
диспетчер посылает уведомление в сервисы/сервис которые обладают этими данными, на самом деле диспетчер не знает, кто обладает этими данными, диспетчер просто знает, что сервис B попросил уведомлять его о событиях «нужны данные Y»
сервис или сервисы B получив такое уведомление, формируют данные и в событии отправляют их в диспетчер, кто быстрее тот и молодец, диспетчер перенаправляет событие с данными в запросивший сервис А. Это позволит избежать ожиданий, когда один сервис висит ожидая данных от другого, к тому же есть возможность получить данные от того сервиса кто сделает это быстрее. Но пока мы в случае когда необходимо просто получить данные другого сервиса у нас выполняется прямой запрос из одного сервиса в другой, таких вызовов очень немного и м не хотим чтобы их кол-во росло, потому что сложнее за всем этим потом следить.
claygod извиняюсь за задержку.
в одном комментарии пожалуй все сразу не расскажешь. У нас есть некоторые требовавния к микросервисам, они должны предоставлять HTTP API, они должны слать метрики и должны слать логи. Эти требования обязательные. Все остальное в общем достаточно гибко, каждый микросервис может быть реализован на каком-то своем технологическом стеке, в том числе и каждый сервис сам решает нужна ли ему внутри очередь или нет. В какой то мере диспетчер выполняет роль очереди на уровне приложения, тем не менее каждый микросервис может использовать свои очереди, есть доступные инструменты, которые уже настроены и работают и которыми может воспользоваться любой сервис, например RabbitMQ.
Микросервисы у нас по своей природе асинхронные, более того все общение происходит через диспетчер, куда каждый микросервис посылает событие при изменившемся состоянии, на это событие могут реагировать другие сервисы в результате чего также изменят свое состояние и кинут свое событие в диспетчер.
на счет REST не могу сказать, что мы во всех микросервисах остались в рамках идеально чистого REST, но в целом это работает, что касается асинхронных задач, то в целом наш подход похож на описываемый здесь http://restcookbook.com/Resources/asynchroneous-operations/
вебсокеты не применяли, пока. В ближайшее время выкатим решение на SSE
Да, в некоторых случаях контейнеры слушают внешние интерфейсы железки и да это неудобно, согласен.
Когда начинали небыло столь широкого выбора инструментов готовых для запуска в проде, поэтому сделали так. Сейчас собираемся менять схему, пока не решили, что имено будем использовать, смотрим в сторону docker swarm и kubernets, так что этот выбор нам еще предстоит сделать. Ну а когда решим готовы поделиться опытом, ну и чужой опыт всегда ценим, лучше учиться на чужих ошибках, чем на своих
1. нет nginx не на каждом хосте, то есть возможны например варианты поднимаем инстанс на амазоне, запускаем в нем один контейнер, добавляем запись в апстрим, то есть по сути nginx может быть вообще один, который проксирует запросы контейнерам в том числе и на разных машинах, но на деле получается nginx не один. Админим не руками, ansible'ом, пока мы небольшие справляемся.
2. И тут ansible и опять же пока мы небольшие этого достаточно, но в целом экспериментируем и ищем пути большей автоматизации с целью большего исключения человеческого фактора. Переключаем тоже используя ansible, в идеале конечно настроить автоматическое переключение и откат в случае неудачи. То есть например при запуске нового контейнера (green) автоматом часть трафика заруливается на него, в течении какого то времени собираются метрики и если заданные пороговые значения не превышены, то старый контейнер (blue) выключается, а весь трафик заруливается на green, если же пороговые значения превышены, то весь трафик возвращается на blue. Но пока делаем это руками используя ansible
3. Мониторинг, тут есть какбы две стороны медали, первая сервис сам о себе шлет некоторый набор метрик — это первая сторона и вторая — это действительно нужен отдельный процесс, но не нужен в том же контейнере, у нас запущен в отдельном контейнере c -v /var/run/docker.sock:/var/run/docker.sock --pid=host чего в большинстве случаев достаточно для мониторинга, по крайней мере нам пока хватает.
А вот на счет идеологии докера, к сожалению мы иногда ее нарушаем, хотя стараемся этого не делать. Приведу пример плохой практики случавшейся у нас: — микросервис на php, который также требует запуска некоторой команды по расписанию, выполнено в виде контейнера с php-fpm и кроном. Сейчас наученные опытом мы избегаем таких решений, но тем не менее это случалось в нашей практике и до сих пор остались микросервисы работающие в таком исполнении.
4. и да и нет, сервисы свои логи пишут в логстеш, есть еще datadog, который имеет готовые решения для логирования некоторых событий докера
Ну на самом деле почти каждый из этих вопросов тянет на отдельную статью. Но если коротко, то:

1. docker-контейнеры на нескольких серверах — это вообще отдельная большая история в том числе и для нас, есть куда развиваться и что еще попробовать, но в общих чертах все запросы проходят через reverse proxy то есть поднимая еще одну копию сервиса в докер контейнере мы просто дописываем нужную запись в upstream у nginx, при этом физически два контейнера могут быть запущены на разных машинах

2. запускаем ли одинаковые микросервисы на одном хосте? Да, как минимум при деплое, я упоминал, что мы используем BGD технику, которая подразумевает в какой то момент работу параллельно двух контейнеров одного микросервиса.

3. Для мониторинга у нас используется DataDog, influxdb + grafana, но мы продолжаем экспериментировать в этом направлении, потому как мониторинг при нашем подходе очень важная составляющая, собираемся попробовать prometheus

4. Логируем logstash + Elastic но тоже продолжаем экспериментировать в этом направлении, эта связка не очень нравится своей громоздкостью, но пока не нашли достаточно надежной, обкатанной альтернативы
Диспетчер построен на базе RabbitMQ плюс некоторые свои обвязки позволяющие либо объединять некоторые потоки событий, либо наоборот дробить в зависимости от нагружености и потребностей. События диспетчер рассылает используя push подход, то есть сервису не нужно держать коннект с диспетчером.
Да, это бесспорно очень полезная фича, если работает как надо, думаю в шторме она хорошо работает. Но не знаю как у других, на моей практике выходит так, что во-первых «на 10 уровней» это как бы повод задуматься над кодом, в других случаях (где отсутствует 10-ти уровневая иерархия наследования) более простых инструментов навигации по коду вполне хватает, с подсказками при написании кода тоже самое, кто-то использует их и в виме, я не использую совсем, отвлекают.
Вполне могу допустить, что IDE будет выигрывать если меняешь проекты часто, скажем каждые пару месяцев.
В случае долго-живущих крупных проектов, в моем случае, выходит так что на первое место выходят другие фичи, которые необходимы, удобство и быстрота написания кода, гибкость в настройке под себя, взаимодействие с внешним миром (в виде различных сторонних утилит, сервисов и прочего) которые так же можно быстро настроить под себя или реализовать.
И да, мой случай это не только я :) но все те люди с которыми я либо работаю вместе, либо общаюсь весьма часто.

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

опять же повторюсь IDE выиграет, когда программисту нужна помощь при написании кода, а когда такая навязчивая помощь чаще мешает, чем помогает, то хочется как можно меньше вторжений извне во время изложения идеи в коде.
За Sublime не скажу, потому как мы используем vim, сказал об этом выше, так вот vim позволяет настроить всё под свои привычки и максимально повысить продуктивность. Все кто у нас сейчас работают с vim и все кто у нас сейчас не работают, но продолжают использовать vim в других компаниях пришли к нему через использование тех или иных IDE, никто не вернулся обратно.

Сравнивать текстовый редактор с IDE как продукт, вообще не имеет смысла, а вот в рамках удобного использования и повышения продуктивности очень даже можно. В руках новичка, либо средней руки программиста IDE выиграет, а вот в руках опытного профессионального разработчика не факт. В случае с моим отделом IDE проигрывает в 100% случаев.
да, но зачастую в руках профессионала SublimeText выигрывает у PhpStorm.
у нас в отделе все работают в vim, не потому что он бесплатен, а потому что так уж сложилось, что он прижился проще и лучше остальных.
вы видимо никогда не работали строителем :)
1

Information

Rating
Does not participate
Location
Россия
Date of birth
Registered
Activity