Здравствуйте, Sharov, Вы писали:
НС>>Почему не та самая? Это какая то другая асинхронность? S>Для асинхронности TPL и параллельность не нужна.
А кто говорил что нужна?
НС>>Ну то есть таски это таки не продолжения? S>Как можно было сделать такой вывод из примера, где по бизнес логике нам нужна одна таска -- я
Как можно было не сделать такой вывод?
S>>> Parallel.For к чему? НС>>К тому что это, внезапно, часть TPL и там внутри таки таски, довольно интересно устроенные. S>И?
И это ответ на твой вопрос.
S>>>Путаете продолжение -- структура данных, НС>>Я ничего не путаю, я просто пытаюсь объяснить понятным тебе языком. Структура данных там IEnumerable. S>Структура данных там КА,
Нет. Это детали реализации.
НС>>Но без вставок императивного кода это не продолжение. S>Не совсем понял, причем здесь вставки императивного кода,
Потому что иначе продолжением можно назвать вообще любую структуру данных.
S>но таки yield -- это не продолжение.
Это именно что классический пример продолжения.
S> Yield не про многопоточность, поток там будет один.
А с чего ты взял что продолжения это про многопоточность? Ознакомься уже с определением, иначе этот разговор так и останется разговором слепого с глухим.
Здравствуйте, ksandro, Вы писали:
НС>>Да. И меньше накладных расходов на создание. K>Да, расходов на создание меньше, но это не слишком важно,
В ряде задач это очень важно.
НС>>И меньше затрат на межпроцессное взаимодействие, так как адресное пространство общее. K>Почему это их меньше?
Потому что адресное пространство общее.
K> Любое взаимодействие в общем случае происходит через некоторую область памяти, доступ к ней надо синхронизировать.
shared mem приводит ровно к тем проблемам, которых ты хочешь избежать отказом от потоков. А все остальные способы на порядки медленнее.
K> Более того, общее адресное пространство скорее увеличивает вероятность всяких неочевидных проблем, таких проблем как например false sharing.
Жизнь вообще — боль.
НС>>Поскольку из линуха потоки так и не выпилили, хотя там разница по ресурсам между процессом и потоком минимальна из всех популярных ОС, то практика нам как бы говорит что плюсы есть и существенные. K>Вот интересно, что это за существенные плюсы?
Решил пойти по кругу?
НС>>Подавляющее большинство ошибок порождается чисто логическими проблемами — гонками и дедлоками. От того что ты разнесешь код на разные процессы эти проблемы никуда не денутся. K>Да, это чисто логические проблемы, но на приактике любое обращение к любой переменной может в итоге вызвать race condition,
На практике ряд несложных правил позволяет в большинстве случаев избежать этих проблем.
K> в сколь-нибудь большом проекте практически невозможно определить, что к переменной нет обращения из другого потока,
Это очень печальный проект, который, вне зависимости от наличия потоков, обречен на суровые проблемы.
K> и даже если его действительно нет, то ты никак-не гарантируешь, что кто-нибудь не обратится к ней в будущем.
K>>>Так что ИМХО плюсы от более быстрого создания потока по сравнению с процессом, никак не могут превесить огромное количество трудноотлавливаемых ошибок, которое мы получаем из за общего адресного пространства.
S>> Вообще то используются пулы потоков, на коих и работает асинхронность и TPL
K>Ну, вот я говорю, что для большинства задач пулы потоков вполне можно заменить на пулы процессов. K>А ассинхронность внутри процесса вполне можно реализовывать с помощью кооперативной многозадачности, что вроде и должны помогать сделать все эти "новомодные" async await (хотя концепция корутин очень старая).
Ты так и не пояснил, чем потоки в процессах лучше множества потоков в одном процессе? Все равно нужны общие данные и их блокировка при чтении и записи.
Я еще понимаю когда нужна изолированность. То есть можно выгрузить и загрузить процеес (домен).
Но когда большая часть кода используют Данные только для чтения, то каков смысл в процессах.
Опять же если нужна запись то придется работать с базой данных, но это ничем не лучше чем работа из разных потоков.
Ну а для блокировки есть куча других методов для блокировки кроме WaitHandle SpinLock,
Проблемы возникают тогда, когда код изначально написанный для однопотока, начинают использовать в многопоточном коде для доступа с статическим переменным
и солнце б утром не вставало, когда бы не было меня
_>Ну вот например при программирование на языке Rust, компилятор гарантирует тебе отсутствие таких проблем.
Тут согласен! Я изначально не говорил что потоки не нужны вообще, но они не нужны в том виде, в каком они реализованы в майнстрим языках. К сожалению rust не майнстрим язык, на нем мало что пишут. Ну и я на практике с потоками на rust не игрался, поэтому не факт, что там не будет своих подводных камней. Я очень надеюсь, что потоки в rust окажутся действительно реализованными правильно, и это будет просто супер!
_>А таких "специалистов" лучше вообще не допускать до программирования.
Ну, на первый взгляд всегда кажется, что у всех кругом кривые руки. А вот попробуй на практике поправить что-нибудь в legacy коде, который активно использует многопоточность...
_>Т.е. я правильно понимаю, что ты предлагаешь использовать необходимость написания ненужного кода по организации междпроцессного взаимодействия в качестве инструмента исправляющего неумение некоторых программистов организовывать свой код? )))
Тут такое дело, что ИМХО 90% программистов не умеет организовывать многопоточный код. Вообще, если ты умеешь его организовывать, может напишешь, как это делать правильно, мне было бы очень интересно, так как я нормально с многопоточностью работать так и не научился. Если ты нашел способы, как избежать ошибок синхронизации, это будет просто супер!!! Только не надо маленьких примерчиков. Мне интересна такая ситуация, вот есть у тебя давно написанная кем-то большая программа с кучей кода, которая и еще использует разные библиотеки, тебе надо добавить в нее маленькую фичу, вроде делается все просто, вызвать пару функций, удалить пару элементов из одного контейнера, добавить в другой. Но потом понимаешь, что где-то там в дебрях этой проги используются потоки. Было бы очень интересно узнать можно ли как-то гарантированно проверить, что твои маленькие изменения не вызовут race condition?
И да, в случае использования процессов тебе приходится потратить время на межпроцессное взаимодействие, да, есть накладные расходы на создание процесса, но ты получаешь хоть какую-то защиту от ОС от ошибок синхронизации. Реализация многопоточности в мейнстрим языках не просто не дает защиты отнеправильного использования потоков, но наоборот способствует и поощьряет его.
Во многих случаях параллельное исполнение вообще не нужно, нажен просто неблокирующив ввод вывод, что вполне можно сделать без многопоточности. Это не очень просто, но все-таки не так опасно как работа с потоками.
K>Во многих случаях параллельное исполнение вообще не нужно, нажен просто неблокирующив ввод вывод, что вполне можно сделать без многопоточности. Это не очень просто, но все-таки не так опасно как работа с потоками.
В большинстве параллельный код вообще не блокирует операции записи. Там где есть запись обычно работают блокировки ReadWriteLock
То есть о каком то замедлении вообще речи не идет. Записей значительно меньше чтения. И ради этого специально тормозить код для межпроцесного взаимодействия.
Не слишком ли жирно?
Есть куча потокобезопасных классов https://docs.microsoft.com/ru-ru/dotnet/api/system.collections.concurrent?view=netcore-3.1
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, ksandro, Вы писали:
S>> Вообще то используются пулы потоков, на коих и работает асинхронность и TPL
K>Ну, вот я говорю, что для большинства задач пулы потоков вполне можно заменить на пулы процессов.
Это только если у тебя задачи маленькие.
K>А ассинхронность внутри процесса вполне можно реализовывать с помощью кооперативной многозадачности, что вроде и должны помогать сделать все эти "новомодные" async await (хотя концепция корутин очень старая).
У того, кто не может написать на потоках без дедлоков и гонок, будут такие же проблемы с любой многозадачностью. Не имеет значения, как ты эту многозадачность назовёшь и во что обернёшь.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>В ряде задач это очень важно.
Ок, было бы интересно узнать, что это за задачи. Честно говоря, не встречал еще задачи, где было бы оправдано постояние создание/убийство множества потоков.
K>>Почему это их меньше?
НС>Потому что адресное пространство общее.
А вот я говорю, что затрат в большинстве случаев или наоборот больше или столько же именно за счет общего аресного пространства.
НС>shared mem приводит ровно к тем проблемам, которых ты хочешь избежать отказом от потоков. А все остальные способы на порядки медленнее.
Да, оно приводит к тем же проблемам, но есть одно отличие. Если мне надо поменять в коде одну переменную, я, просто читая код, смогу за конечное и время определить, что она на самом деле создана в shared memory, и соответственно буду правильно с ней работать. Чтобы понять в каком потоке используется переменная, как правильно практически невозможно. Я конечно понимаю, что в твоем коде, все сразу ясно и нет никаких проблем. Но программисты часто работают со старым кодом, и с кодом большого объема.
K>> Более того, общее адресное пространство скорее увеличивает вероятность всяких неочевидных проблем, таких проблем как например false sharing.
НС>Жизнь вообще — боль.
Мыши плакали и кололись, но продолжали жевать кактус. Вообше не понимаю, почему программисты так любят многопоточность и впихивают ее везде и где надо и где не надо.
НС>>>Поскольку из линуха потоки так и не выпилили, хотя там разница по ресурсам между процессом и потоком минимальна из всех популярных ОС, то практика нам как бы говорит что плюсы есть и существенные. K>>Вот интересно, что это за существенные плюсы?
НС>Решил пойти по кругу?
Просто ты привел стандартный аргумент религиозных споров. Раз уже давно есть религия, церкви и священники — значит и Бог есть. Как-то мне не очень нравится, что потоки превращаются в религию.
Так что аргумент, что потоки нужны потому, что их не выпилили, как-то не очень убедительно смотрится. Ты все же распиши эти существенные плюсы...
НС>>>Подавляющее большинство ошибок порождается чисто логическими проблемами — гонками и дедлоками. От того что ты разнесешь код на разные процессы эти проблемы никуда не денутся. K>>Да, это чисто логические проблемы, но на приактике любое обращение к любой переменной может в итоге вызвать race condition,
НС>На практике ряд несложных правил позволяет в большинстве случаев избежать этих проблем.
Буду тебе очень благодарен, если ты приведешь этот ряд несложных правил! Если действительно поможет избежать проблем, особенно с legacy кодом, то это будет просто бомба!
K>> в сколь-нибудь большом проекте практически невозможно определить, что к переменной нет обращения из другого потока,
НС>Это очень печальный проект, который, вне зависимости от наличия потоков, обречен на суровые проблемы.
Как ты говоришь, жизнь боль. Не всегда есть возможность выбрать новый проект с красивым кодом. В случае других проблем, баги всегда можно отловить, код зарефакторить, но если не правильно организована работа с потоками, то привести это в порядок, практически невозможно. Отлавливать ошибки многопоточности можно годами. Причем, даже в хорошем коде, даже написанном крутыми гуру, ошибки многопоточности все равно находятся. И находятся они через несколько лет, в самый неподходящий момент.
K>> и даже если его действительно нет, то ты никак-не гарантируешь, что кто-нибудь не обратится к ней в будущем.
НС>В твоем языке нет private?
Это что-то новое... Не поделишься как именно private решает проблему доступа из другого потока. Гарантировать можно только потокобезопасность локальной переменной функции. А private никак к многопоточности не относится. Если эту переменную можно как-то менять, то ее можно менять из разных потоков.
Я не отрицаю, что потоки могут быть полезны в ряде задач, но таких задач, где плюсы реально перевешивают минусы крайне мало, просто все привыкли, что надо юзать многопоточность и даже не пытаются думать о других вариантах решения проблемы.
Здравствуйте, alex_public, Вы писали:
_>Это ты тут свой опыт пересказываешь? ) Ну возможно стоило в начале ознакомиться с теорией вопроса (общепринятыми практиками решения этих проблем), а потом уже собственно кодить? )
В том числе и мой, но не только мой. Теория-то в общих чертах более менее понятна, но по многопоточности написано огромное количество книг и статей, и все продолжают писать и писать. Более того, регулярно выясняется что даже крутые гуру многопоточности в своих статьях таки делают ошибки. И как раз крутые гуру многопоточности всегда говорят, что многопоточность — это очень и очень сложная вещь. Реальные проблемы с многопоточностью обычно начинаются, код обрастает функциональностью, и его размер увеличивается его начинают править разные люди, пусть даже грамотные.
Но вообще я буду рад, если ты приведешь подборку материалов про best practices в многопоточности, может там будет что-то новое для меня...
_>У процессов по сравнению с потоками есть только недостатки и ни единого достоинства. Если в задаче требуется использование общей памяти разными задачами, то тебе в любом случае понадобятся какие-то примитивы синхронизации. Что между процессами, что между потоками. И единственная разница будет в том, что в случае процессов тебе придётся написать ещё кучи глупого кода по организации общей памяти.
Так или иначе, но для процессов, ОС дает какую-то гарантию от правки данных, которые не находятся в shared memory, в случае потоков (а точнее их реализации в большинстве языков), вся надежда только на внимательность. А если кода много, и его правят разные люди. То по закону мерфи кто-то обязательно рано или поздно напортачит с потоками.
Я не говорю, что процессы — панацея. Но проблемы с потоками имеют свойство долго скрываться и вылезать в самый неудобный момент.
ИМХО очень часто многопоточность используют не для паралеллизма для организации неблокирующего ввода вывода. А это лучше и проще делать в одном потоке.
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>>> Parallel.For к чему? НС>>>К тому что это, внезапно, часть TPL и там внутри таки таски, довольно интересно устроенные. S>>И? НС>И это ответ на твой вопрос.
Хотелось бы деталей про интересности тасков в Parallel.* .
S>>>>Путаете продолжение -- структура данных, НС>>>Я ничего не путаю, я просто пытаюсь объяснить понятным тебе языком. Структура данных там IEnumerable. S>>Структура данных там КА, НС>Нет. Это детали реализации.
IEnumerable это скорее детали реализации. Любая структура данных -- это КА, переход из одного состояния в другое.
НС>А с чего ты взял что продолжения это про многопоточность? Ознакомься уже с определением, иначе этот разговор так и останется разговором слепого с глухим.
Благодарю, отличная ссылка. Т.е. это более фундаментальное понятие, чем ко-рутины. Не знал.
continuation is an abstract representation of the control state of a computer program
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, ksandro, Вы писали:
K>>Во многих случаях параллельное исполнение вообще не нужно, нажен просто неблокирующив ввод вывод, что вполне можно сделать без многопоточности. Это не очень просто, но все-таки не так опасно как работа с потоками. S>В большинстве параллельный код вообще не блокирует операции записи.
Это как? Если так, то операция записи сама по себе потококобезопасная, то есть использует блокировку где-то внутри. Ну или ты пишешь в разные места. Или я что-то недопонимаю, я все таки не .net разработчик
S>Там где есть запись обычно работают блокировки ReadWriteLock
Ужасно тормозная вещь, имеет смысл, толко когда у очень тебя много параллельных чтений и очень мало записей.
S>То есть о каком то замедлении вообще речи не идет. Записей значительно меньше чтения. И ради этого специально тормозить код для межпроцесного взаимодействия. S>Не слишком ли жирно? S> Есть куча потокобезопасных классов https://docs.microsoft.com/ru-ru/dotnet/api/system.collections.concurrent?view=netcore-3.1
Ага, вот сделаем, все коллекции потокобезопасными, а потом не понимаем, почему программа работает медленнее, чем однопоточная.
Я понимаю, что есть куча всяких штук для различной синхронизации. И их количество показывает, что синхронизация, вещь не простая. И они никак не могут меня защитить от случайного обращения к одной области памяти из разных потоков. Я должен их явно ставить.
Но вообще рекомендую попробовать переписать твою прогу в один поток, почему-то мне кажется что у тебя все заработает в разы быстрее...
Здравствуйте, Sharov, Вы писали:
НС>>В документации максимальные размеры пулов и алгоритмы их вычисления не указаны, т.е. они могут меняться. S>На гитхабе подсмотреть нельзя?
Подсмотри. А потом смотри заново с каждым релизом. Аналогично с хинтами aggressive inlining/optimizations. Последний если подсмотреть, то окажется что оно вообще не связано с оптимизациями пока что.
Здравствуйте, Sharowarsheg, Вы писали:
S>Здравствуйте, ksandro, Вы писали:
S>>> Вообще то используются пулы потоков, на коих и работает асинхронность и TPL
K>>Ну, вот я говорю, что для большинства задач пулы потоков вполне можно заменить на пулы процессов.
S>Это только если у тебя задачи маленькие.
А чем большие задачи отличаются в данном контексте?
K>>А ассинхронность внутри процесса вполне можно реализовывать с помощью кооперативной многозадачности, что вроде и должны помогать сделать все эти "новомодные" async await (хотя концепция корутин очень старая).
S>У того, кто не может написать на потоках без дедлоков и гонок, будут такие же проблемы с любой многозадачностью. Не имеет значения, как ты эту многозадачность назовёшь и во что обернёшь.
Даже крутые гуру часто не могут написать, что-то более менее сложное без дедлоков и гонок.
Есть одна разница, то, что у тебя есть гонки, ты можешь узнать только через год стабильной работы на продакшене, когда уже разработчик давно уволился. Это главная фишка дедлоков и гонок, что они могут не отлавливаться даже очень тщательным тестированием.
с кооперативной (невытесняющей) многозадачность, все тоже непросто, но тестирование все-таки отлавливает большинство багов.
Здравствуйте, ksandro, Вы писали:
K>Здравствуйте, Serginio1, Вы писали:
S>>Здравствуйте, ksandro, Вы писали:
K>>>Во многих случаях параллельное исполнение вообще не нужно, нажен просто неблокирующив ввод вывод, что вполне можно сделать без многопоточности. Это не очень просто, но все-таки не так опасно как работа с потоками. S>>В большинстве параллельный код вообще не блокирует операции записи. K>Это как? Если так, то операция записи сама по себе потококобезопасная, то есть использует блокировку где-то внутри. Ну или ты пишешь в разные места. Или я что-то недопонимаю, я все таки не .net разработчик
Операция записи как раз не потокобезопасна. Пример из БД. Тебе нужно модифицировать набор записей в котором присутствует касаа.
Ту не можешь одновременно списать деньги, так как их может оказаться меньше. Но транзакция может и не завершиться если по каким то причинам эти деньги спасить нельзя.
И вторая транзакция должна дождаться, пока первая не закомитится или не откатится.
Если ты используешь общие данные только на чтение, то и блокировки не нужныю
S>>Там где есть запись обычно работают блокировки ReadWriteLock K>Ужасно тормозная вещь, имеет смысл, толко когда у очень тебя много параллельных чтений и очень мало записей.
Так так в большинстве и существует. Берем ту же БД и транзакции. Там в большинстве блокировки именно на чтение
S>>То есть о каком то замедлении вообще речи не идет. Записей значительно меньше чтения. И ради этого специально тормозить код для межпроцесного взаимодействия. S>>Не слишком ли жирно? S>> Есть куча потокобезопасных классов https://docs.microsoft.com/ru-ru/dotnet/api/system.collections.concurrent?view=netcore-3.1 K>Ага, вот сделаем, все коллекции потокобезопасными, а потом не понимаем, почему программа работает медленнее, чем однопоточная.
Прежде чем говорить, ты бы пример привел где эти тормоза. Вот взаимодействие между процессами там дикие тормоза получишь.
K>Я понимаю, что есть куча всяких штук для различной синхронизации. И их количество показывает, что синхронизация, вещь не простая. И они никак не могут меня защитить от случайного обращения к одной области памяти из разных потоков. Я должен их явно ставить.
Но если ты используешь асинхронность тот же JavaScript работает на одном потоке, но доступ к данным из разных задач не защищен.
Ничего не надо ставить если используешь только чтение.
K>Но вообще рекомендую попробовать переписать твою прогу в один поток, почему-то мне кажется что у тебя все заработает в разы быстрее...
Угу проходили. У меня на некоторых вещах стоят семафоры, что бы контролировать количество одновременных запросов. Поверь ты не прав.
Многопоточность дает практический пропорциональный прирост количеству потоков (по количество ядер + учет хайпертридинга и на самом деле больше так как внутри могут выполняться асинхронные запросы к диску, БД на другом компе итд)
Просто они нужны, что бы давать отлуп иначе сервер не справляется аля DoS-атака.
Еще раз в большинстве случаев происходит чтение данных , в большинстве случаев к базе данных. Нет проблем с многопоточностью.
Твои проблемы надуманы.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
K>>Я понимаю, что есть куча всяких штук для различной синхронизации. И их количество показывает, что синхронизация, вещь не простая. И они никак не могут меня защитить от случайного обращения к одной области памяти из разных потоков. Я должен их явно ставить. S> Не синхронизация, а блокировки. Синхронизация это выполнение кода в одном потоке.
Как бы странно это ни звучало, но в отношении потоков — синхронизация — это акт/процесс, позволяющий выполнять параллельные потоки синхронно (согласованно). Внезапно да? Выполнение кода в одном потоке — это может быть тоже отсюда, но блокировки — это механизм синхронизации же.
Здравствуйте, ksandro, Вы писали:
K>Как ты говоришь, жизнь боль. Не всегда есть возможность выбрать новый проект с красивым кодом.
И ради кривых проектов ты предлагаешь запретить потоки? Ну ОК, я тебя понял.
НС>>В твоем языке нет private?
K>Это что-то новое... Не поделишься как именно private решает проблему доступа из другого потока. Гарантировать можно только потокобезопасность локальной переменной функции. А private никак к многопоточности не относится. Если эту переменную можно как-то менять, то ее можно менять из разных потоков.
private гарантирует что менять переменную будет только известный код. А от этого уже проистекает как бороться. Можно в документации написать крупными буквами, что класс с изменяемым состоянием не потокобезопасен, и стараться поменьше таких классов плодить. Для большинства адекватных разработчиков этого вполне достаточно. Можно статическую переменную сделать thread static, и тогда эту будут личные половые проблемы того криворукого программиста, что стейт в другом потоке другой. Наконец, в совсем клинических случаях можно повесить простенький guard, который будет проверять, что изменяющие состояние методы зовутся из того потока, из которого нужно (к примеру, из того из которого позвали ктор), и тогда криворучки будут получать исключение при попытке написать кривой код.
Здравствуйте, Mystic Artifact, Вы писали:
MA>Здравствуйте, Serginio1, Вы писали:
K>>>Я понимаю, что есть куча всяких штук для различной синхронизации. И их количество показывает, что синхронизация, вещь не простая. И они никак не могут меня защитить от случайного обращения к одной области памяти из разных потоков. Я должен их явно ставить. S>> Не синхронизация, а блокировки. Синхронизация это выполнение кода в одном потоке. MA> Как бы странно это ни звучало, но в отношении потоков — синхронизация — это акт/процесс, позволяющий выполнять параллельные потоки синхронно (согласованно). Внезапно да? Выполнение кода в одном потоке — это может быть тоже отсюда, но блокировки — это механизм синхронизации же.
Согласен. Перемкнуло
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, ksandro, Вы писали:
K>>>Ну, вот я говорю, что для большинства задач пулы потоков вполне можно заменить на пулы процессов. S>>Это только если у тебя задачи маленькие.
K>А чем большие задачи отличаются в данном контексте?
Накладные расходы на всякую мелочёвку начинают отнимать неделю времени вычисления.
S>>У того, кто не может написать на потоках без дедлоков и гонок, будут такие же проблемы с любой многозадачностью. Не имеет значения, как ты эту многозадачность назовёшь и во что обернёшь.
K>Даже крутые гуру часто не могут написать, что-то более менее сложное без дедлоков и гонок.
Это только первые десять лет сложно.
K>Есть одна разница, то, что у тебя есть гонки, ты можешь узнать только через год стабильной работы на продакшене, когда уже разработчик давно уволился. Это главная фишка дедлоков и гонок, что они могут не отлавливаться даже очень тщательным тестированием.
Дедлоки и гонки не нужно выявлять тестированием, их нужно не создавать при проектировании.
N>>Короче, я за асинхронность. TB>Какого хрена "синхронное плаванье" — это когда тётки одновременно в воде кувыркаются, а синхронное программирование — это когда процессы по очереди выполняются? Диаметрально противоположный смысл.
Синхронность значит два процесса жестко связаны между собой по времени. Тетки выполняют одинаковые движения в одинаковые моменты времени, задачи — начинают исполнение именно в момент завершения другой задачи.
Вообще вот определение из которого оно перекочевало в программирование.
Как много веселых ребят, и все делают велосипед...