Обновить

Комментарии 17

"во-первых нет, в Go нет асинхронности, во-вторых нужна синхронизация"

Интересно, что было в голове у составителя ответа? Если нет асинхронности, то значит все синхронно и в таком случае никакая дополнительная синхронизация нигде не нужна. :)

Да вроде есть, если определять ее как "переключение между лёгкими задачами без блокировки OS потоков"

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

Боже, да, синхронизация не нужна тогда аххахааххахаха

Попробую найти составителей опросника, может получится вразумить...

Асинхронность в первую очередь про освобождение системных ресурсов при ожидании ответа.

А как результат эта внутренняя кооперативная многозадачность даёт лёгкость в переключении, позволяющая M*N - M зелёных задач на N системных потоков, переключение в user space по await-точкам, без вытеснения.

Если к функциям дописать ключевое слово go, они будут работать асинхронно?

Как рантайм решит

То есть при возврате может сменить системный поток, а может и остаться на старом. Но дебагеру это всё равно, он работает прозрачно

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

в мире высокого уровня может и так, но асинхронность и параллельность это разное.

Например классический RTOS - асинхронность можно строить даже на одноядерном процессоре. Но вот параллельность только если используются разные вычислительные ядра и на низком уровне нужно будет руками нарезать и раскидывать задачу что бы она параллельно выполнялась в разных потоках

Несомненно, да, ты прав. Но скажи, задача, которая выполняется в разных потоках, - она выполняется асинхронно?

Асинхронность предполагает точку синхронизации (когда можно продолжить исполнение след. куска подзадачи - пресловутые await), если нет синхронизации, то её нет. Разрыв исполнения подзадачи должен быть, чтобы назвать её асинхронной. Параллельность - про исполнение подзадач одновременно. Без синхронизации в ней не будет асинхронности, т.к. не будет логического разрыва исполнения.

Из твоей логики получается, что две задачи, работающие в двух разных потоках на двух разных ядрах процессора, не имеющие общей точки синхронизации, работают синхронно? Так?

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

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

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

Так у нас и не спор, получается, а банальная несостыковка в понятийном аппарате :)

Есть ещё синхронная параллельность в виде SIMD и ILP, например. Или стрельба через винт как механическая аналогия.

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

а потом приходит Эндрю Келли, выдает спич про асинхронность через I/O интерфейсы, и выносит окрашивание асинка на свалку истории.

Асинхронность

Метод выполнения задач, при котором IO bound операции не блокируют основной поток. Иными словами в современном мире это просто выполнение задач в корутинах/горутинах/виртуальных потоках.

Мне кажеться Вы сами запутались.

Асинхронность - это свойство частей исполняемого кода работать не последовательно. Например, у нас есть задача A, для ее вычисления нам нужно еще вычислить задачи B и C. Если мы вычисляем их последовательно: A1->B->C->A2 то получаем синхронно выполненный набор задач. Если же мы ломаем последовательность вычислений, продолжая вычислять работу которая должна быть после B и C еще не имея результатов тех самых B и C , то получаем непоследовательно, ну или не синхронно ну или асинхронно выполненную последовательность.

Причем здесь IO bound задачи в определении асинхронности? Я с тем же успехом могу асинхронно считать алгоритмы в разных потоках выполняя работу параллельно или не параллельно в одном, и так и так работа будет асинхронной.

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

Если все же немного выглянуть из мира пайтона, то базовый пример асинхронности - аппаратные прерывания. Выше уровнем - разные софтверные прерывания, сигналы и коллбэки со стороны операционки. Еще выше, использование операционкиных сисколлов со стороны пользовательских программ. Вот так например я могу вызвать у себя асинхронную запись буфера:
sys$qio(EFN$C_ENF, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf, buf_size);
Причем вместо 0, 0, можно указать что-то типа my_ast, magic_number и тогда по завершению записи функция my_ast получит Asynchronous System Trap со стороны системы.
И только потом уже начинается синтаксический питоновский сахар.

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

Мне кажется, что есть конфликт между целями

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

  • нужно продать свою идею заведомо некомпетентным вне финансового схематоза начальничкам, для чего её тоже нужно объяснять, но иначе, причём как есть - слищком просто

  • нужно максимально затруднить и/или отсрочить осознание сделанного другими продавцами змеиного масла подателями свежих идей, но публично объяснять всё равно приходится, причём как есть - худший вариант.

Поэтому и объяснения от кухни, и маркетинговые названия: го-рутины, ко-рутины, жаль си-рутины не успели к этому празднику жизни. Конечно, такое обязано раздражать.

Я бы начал объяснение с того, что из последовательного расположения строчек кода никак не следует существование однопотока в Природе и задача не в том, чтобы организовать многопоточность, а чтобы применить абстракцию однопоточного выполнения. И перешёл бы к аппаратным прерываниям. Далее к JavaScript (а не Python ибо разбираться с доведённым до логического завершения проще) и Go. И поимел бы большие проблемы по трём приведённым выше причинам.

Спрашивать вещи типа вопроса с собеседования по Go, как по мне, наиболее ужасно как раз потому, что гениальность подхода Go и состоит как раз в том, что о таком вообще не нужно думать. Не потому, что исчез повод не нужно, а потому, что следует осознанно воздерживаться от. Есть горутины и каналы - вот и ладненько. И не спрашивать как именно якобы по природе многопоточный Go работает в принципиально однопоточной WebAssembly - работает однако, вот и пусть.

В Go удалось Event Loop и Multi-threading описать одним формализмом - это чисто математическое достижение. Радуемся.

К слову

Со времён знакомства с Go сохранилась функция

func fgrm(n int, ch chan int64, d int, dLim int) (res int64) {
	if n < 3 {
		if ch != nil {
			ch <- 1
			close(ch)
		}
		return 1
	}
	if d < dLim {
		//runtime.LockOSThread()
		c1 := make(chan int64, 200)
		c2 := make(chan int64, 200)
		go fgrm(n-1, c1, d+1, dLim)
		go fgrm(n-2, c2, d+1, dLim)
		res += <-c1
		res += <-c2
	} else {
		res = fgrm(n-1, nil, n+1, dLim) + fgrm(n-2, nil, n+1, dLim)
	}
	if ch != nil {
		ch <- res
		close(ch)
	}
	return
}

Я до сих пор помню удивление как на 4-ядерном процессоре время выполнения бодро падало при росте dLim от 1 до 5, а при переходе от 5 к 6 увеличилось в 250 раз.

Саллахадин Джуба в своей книге про PostgreSQL даёт точное определение асинхронности. Моими словами:

асинхронность - это процесс одновременного выполнения задач, при котором мы точно не знаем: в каком порядке эти задачи завершатся.

Отсюда следуют такие выводы:

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

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

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

В пайтоне GIL не наглухо блокирует потоки. Во второй версии языка он переключал потоки при выполнении определённого количества инструкций. С третьей версии триггер переключения заменён на время выполнения, после которого поток отдаёт управление. Поэтому, многопоточность в пайтоне 100% асинхронная и может применяться на CPU-bound операциях. Но не параллельная, поскольку одновременно выполняется только один поток. Конкурентная. Также, numpy может обходить GIL и делать операции параллельными. Это исключение.

Asyncio - конкурентный, асинхронный. Но передача управления происходит только на IO-bound операциях. Не параллельный никогда.

Асинхронность, конкурентность и параллельность - это стили программирования. Программист выбирает тот подход, который будет использоваться при выполнении его кода. Например, запуская умножение векторов через numpy, он может рассчитывать на параллелизм вычислений. А при использовании asyncio, он должен заботиться о конкурентности. Как и при использовании многопоточности, поскольку его треды могут переключаться.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации