LINUX.ORG.RU

blocking / non-blocking calls

 


1

3

Как известно, вызовы бывают блокирующими и не блокирующими. По умолчанию read() для сокета отправляет процесс в сон, пока в сокет не придёт сколько-то данных. Он блокирует исполнение. Если включить неблокирующий режим, read() вернётся сразу, и по errno можно будет понять, произошла ли ошибка или просто данных пока нет. Возможно, такой read() понадобится вызвать несколько раз.

Раньше разделения на blocking и non-blocking хватало. Но потом пришли всякие языки со встроенными фиберами, типа Go, где можно писать обычный блокирующийся код, который на самом деле только выглядит блокирующимся, а на самом деле внутри неблокирующийся. Если данных в сокете недостаточно, среда исполнения откладывает текущий фибер и возобновляет какой-нибудь другой, которому данные уже пришли.

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

В случае с Go этот вопрос может не стоять насколько остро. Программа на Go запускает несколько системных потоков, и так сглаживает ожидание. Но, скажем, в OpenResty такого нет. Там важно знать, заблокируется ли нить или нет, потому что она одна.

★★★★★

Я в ваших го и тредах ничего не понимаю, но

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

А в жабоскрипте/питоне аналог файберов - это async/await, нет? Называть эти вызовы асинхронными?

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

Постой, разве у тебя путаница возникла не от того, что в го дофига тредовфайберов? Если в openresty такого нет, то откуда проблема?

anonymous ()

Называть эти вызовы асинхронными?

Использование «синхронный»/«асинхронный» ту же путаницу вносит.

Если в openresty такого нет, то откуда проблема?

Там есть нечто, что они называют light thread. Они сделаны на корутинах Lua. В них можно соединиться с чем-нибудь по TCP и обмениваться данными. Код выглядит синхронным, но при этом не блокирует выполнение основного цикла Nginx. Таких light thread можно создавать много. При этом с точки зрения системы процесс однопоточный.

i-rinat ★★★★★ ()
Последнее исправление: i-rinat (всего исправлений: 1)
Ответ на: комментарий от tyamur

Использование «синхронный»/«асинхронный» ту же путаницу вносит.

Не вижу путаницы.

Опиши тогда этими словами read() для сокета, для стандартного режима и SOCK_NONBLOCK, и sock:receive() из openresty.

Если ты не в курсе, openresty это по сути Nginx с прикрученным к нему Lua. Много запросов могут быть в обработке сразу. Чтение из сокетов в обработчике одного запроса не тормозит другой. Но как бы тормозит текущий. Иными словами, можно считать, что sock:receive() не вернёт управление, пока оговорённый объём данных не будет получен.

i-rinat ★★★★★ ()

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

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

anonymous ()

Добавить еще один описательный признак, нет? Синхронный/асинхронный.

фиберами

блокирующийся код, который на самом деле только выглядит блокирующимся, а на самом деле внутри неблокирующийся

асинхронный блокирующий вызов — если потоков > 1, то за счёт этого блокировка нивелируется, т.е. синхронность/асинхронность управляет конечной оценкой вызова, поэтому является внешним описательным признаком

вызовами, которые действительно могут заблокировать выполнение процесса, например, чтение файла с диска

синхронный блокирующий вызов

Virtuos86 ★★★★★ ()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от Virtuos86

асинхронный блокирующий вызов

Слово «асинхронный» уже используется как аналог «неблокирующий»: https://en.wikipedia.org/wiki/Asynchronous_method_invocation

если потоков > 1, то за счёт этого блокировка нивелируется

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

поэтому является внешним описательным признаком

Ну да. Проблема только в том, что люди неправильно понимают. Каждый раз в обсуждениях приходится заново договариваться, что значит «асинхронный». Это утомляет.

i-rinat ★★★★★ ()
Ответ на: комментарий от Virtuos86

Похожая проблема есть со термином «сублимация». Для перехода между жидкостью и газом есть два термина: «испарение» и «кипение». А для перехода между твёрдым телом и газом есть «сублимация» и «сублимация». Можно попытаться зафиксировать термин для одного их процессов, назвав второй другим именем, или добавив ещё какое-нибудь слово. Но оба процесса уже называют «сублимацией», поэтому от путаницы так не убежать.

i-rinat ★★★★★ ()

Golang, blocking

В го хттп или тсп запросы являются по умолчанию блокирующимися (в терминологии самого го). То что при этом может вызываться другая гороутина это дело десятое, сама гороутнина с вызовом будет заблокирована. Если мы хотим контролировать время блокировки можно использовать различные паттерны, например использовать механизм пакета context.

DukeNukem ()
Ответ на: комментарий от i-rinat

если потоков > 1, то за счёт этого блокировка нивелируется

Но ведь не только за этот счёт.

Это был пример реализации асинхронности.

Может, тогда по аналогии с моделями вычисления: энергичный vs. ленивый? Ленивый блокирующий вызов, энергичный блокирующий вызов. Правда, для неблокирующих вызовов критерий ленивый/энергичный плохо применимы.

Virtuos86 ★★★★★ ()
Последнее исправление: Virtuos86 (всего исправлений: 1)

А вот фиг его знает. В высокоуровневых языках такое встречается повсеместно и относится далеко не только к вводу-выводу в сокеты или файлы.

dave ★★★★★ ()

Может добавить префикс и получить что-то вроде «thread-blocking» («потоко-блокирующие»), «fiber-blocking» («волокно-блокирующие»), «asynchronous» («асинхронные»)?

sanwashere ()
Ответ на: комментарий от cvv

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

Что значит почти? Да и операция может быть асинхронной, даже если вызов функции синхронный. Для пояснения опять нужен абзац текста, потому что все понимают слово «синхронный» по-разному.

i-rinat ★★★★★ ()

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

А это вообще как понять? Данный приходят в сокет или в фибер? На шизофазию похоже :)

Deleted ()
Ответ на: комментарий от Deleted

А это вообще как понять? Данный приходят в сокет или в фибер?

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

Наверное, это сложно понять, если не сталкивался.

Работал когда-нибудь с неблокирующим IO на сях? Вот примерно так, только вся морока с возобновлениями скрыта рантаймом.

i-rinat ★★★★★ ()
Последнее исправление: i-rinat (всего исправлений: 1)

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

dave ★★★★★ ()
Последнее исправление: dave (всего исправлений: 2)
Ответ на: комментарий от dave

Время скачет и в обычных потоках. Запустил read() сейчас, проснулся — а уже пару секунд прошло. Или пользователь послал SIGSTOP, и всё вообще повисло на неопределённый срок.

Время монотонное, и это уже хорошо. Требовать чего-то большего смысла нет.

i-rinat ★★★★★ ()
Ответ на: комментарий от invy

Асинхронный?

Чуть ли не автоматически воспринимается как «неблокирующий».

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

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Для пояснения опять нужен абзац текста, потому что все понимают слово «синхронный» по-разному.

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

anonymous ()
Ответ на: комментарий от anonymous

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

Несомненно. Хоть целую страницу. Главное, чтобы не приходилось переобъяснять существующие термины.

Вот есть, к примеру, «фабрика». Если придумать своё какое-то значение, то во время объяснений никого поначалу не смутит это термин, он же понятен и так. Только вот понимать будут совсем не то. Возможно, в какой-то момент до кого-то дойдёт, что части не стыкуются. И тогда снова придётся объяснять и пояснять.

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

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Для Erlang и Racket используют: неблокирующий, блокирующий и блокирующий виртуальную машину.

Иногда синхронный, асинхронный и блокирующий виртуальную машину.

monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 1)

не большой специалист но выскажусь.
если текст программы вокруг этого вызова организован (и это допустим видно) исходя из того, что течение (flow) текста тут останавливается (именно текста программы, а не техн. деталей вроде того, отложит ли процессор этот тред или нет), то сказав async или non-blocking ты всех запутаешь. ведь ход текста на деле будет suspended, а ты им говоришь async
а если же написано async/non-blocking и там в тексте к примеру polling - тогда да, документация будет соответствовать увиденному.
есть еще один довод: если тебе лично покажут изолированно этот текст (где делается вызов), но не расскажут про существование софтварных недо-тредов, то как ты этот вызов будешь воспринимать?
ну или иначе. в программе с полноценными потоками вызовы блокируещие, но они на уровне терминов не становятся менее блокирующими от того факта, что у этого процесса есть и другие треды и на них процессор продолжает иногда переключаться и выполняет их, т.е. это отдельное обстоятельство/детали. иначе можно договориться, что в mutitasking компьютере вообще блокирующего ничего нет.
ну я так думаю. а про то, что nginx не остановится на это время наверное можно где-то примечания делать плюс в introduction объяснить. если есть такие вызовы, которые весь nginx остановят, то наверное тоже примечения делать, мол что весь webserver будет suspended и реквесты не будут served.
но это мое сугубо лично мнение.

entemophyllon ()
Ответ на: комментарий от entemophyllon

ну и опять же смотря какая там фактическая семантика, если при запросе к примеру к БД у тебя на момент выполнения следующей строки текста программы данные уже гарантированно доступны (т.е. их прибытия ждали), то наверное это был не async и не non-blocking.

entemophyllon ()
Ответ на: комментарий от i-rinat

Я скорее о том, что это две области с четкой границей: execution flow на уровне текста программы - это одно, а треды, процессы, ОС, процессор - это другое. Если для execution flow на уровне текста программы этот вызов blocks, то возможно, его можно просто называть «blocking». Что до альтернативного термина: может быть поискать что-то вокруг слов control, flow, fiber, routine, execution, suspend.

entemophyllon ()

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

Вот уж набросил так набросил.

redixin ★★★★ ()
Ответ на: комментарий от redixin

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

После объяснений это становится понятно. Но сначала на «блокирущий» вызов происходит мгновенная реакция: «не-е, нам нельзя блокироваться, у нас один поток. Всё колом встанет.»

Вот уж набросил так набросил.

В качестве наброса тема унылая донельзя.

i-rinat ★★★★★ ()
Ответ на: комментарий от Sorcerer

Откладывающийся, может.

У меня свой вариант был похожий: «yielding call» или «уступающий вызов».

«Откладывающийся» тоже неплохо, но возникает вопрос о том, что дальше происходит, после откладывания.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

на «блокирущий» вызов происходит мгновенная реакция: «не-е, нам нельзя блокироваться, у нас один поток. Всё колом встанет.»

Так оно и так колом встанет если одна сопрограмма. А если их много то как оно может колом встать? Это тоже многозадачность, хоть и кооперативная.

А еще есть такие понятия как конкурентность и многопоточность.

redixin ★★★★ ()
Ответ на: комментарий от redixin

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

Либо нужно, чтобы у человека не было знаний о внутреннем устройстве вообще, и тогда ему можно рассказать, что вот есть потоки исполнения, в которых можно делать read/write. Они вернут управление только когда завершат работу. При этом можно создавать новые потоки исполнения в любой момент. Если не задумываться о деталях, то это вполне понятно.

Либо нужно детальное понимание механизма работы. Что происходит, когда, как.

Проблема в том, что у большинства детального понимания нет, а вместо этого они где-то посередине. Эти дополнительные знания не помогают, они мешают. Вместо того, чтобы выяснить, что в данном случае значит «блокирующий» или «асинхронный», используется существующее знание. Терминология конфликтует, и всё стопорится. Нужно этот недостаток понимания как-то преодолеть. И пока что самая лучшая идея состояла в введении новых названий.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

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

anonymous ()