Эрланг и все-все-все (на самом деле, не совсем)
От: Mamut Швеция http://dmitriid.com
Дата: 25.06.15 14:57
Оценка: 13 (6) +1 -7 :))
Сначала решил ответить где-то в подветке, но решил ответить отдельным топиком, потому что текста много, не хочу, чтобы он терялся во глубине рсдновских руд.

I>Ну да, твоя любимая тактика — один ты умный, а другие даже читать не умеют.


Что поделать, если другие действительно даже читать не умеют.

Последняя попытка. Без надежды на понимание. Убедительная просьба сначала прочитать, а потом отвечать. Хотя лучше не отвечать. «Аргументы» мне давно известны.

Дальше в топике я называю потоками и потоки и корутины и легковесные процессы. Так просто проще.

Итак.

Тема топика: Mногопоточность: C++ vs Erlang vs другие

Начиналось все с этого вопроса: Итак, что там у С++ за траблы с многопоточной невнятностью? И какие языки размалывают его по внятности?

Тезис топика: Для удобного использования многоядерности и вообще распараллеливания чего бы то ни было, нужно иметь, по сути, одну вещь: поддержку этого языком. Когда все, что язык предоставляет — это появившиеся в 2011-м стандарте thread'ы, то далеко на нем не уедешь. Во всяком случае пока умные люди не напишут вокруг всего этого костыли и библиотеки. Потому что первый же залетный mutex.lock убъет любую многопоточность и многоядерность.

Если ты и прочие готовы обсуждать только и исключительно:
— производительность VM Erlang'a, ты не умеешь читать
— особенности VM Эрланга, то ты не умеешь читать
— область применения Эрланга, то ты не умеешь читать
— и т.п.

Потому что топик — ВНЕЗАПНО! — не про Эрланг. Если ты не можешь это понять, то ты не умеешь читать. И понимать.

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

Вводная часть

Практически каждое первое приложение сегодня так или иначе [многопоточно, многоядерно, распределенно] (выбрать любую комбинацию из этого списка). Если нет, то будет в ближайшие годы. Даже самые тупые домашние странички, написанные школотой на PHP управляются весьма себе веб-серверами, заточенными под этот список. Даже самые тупые однопоточные мобильные приложения часто имеют backend где-то в облаках, которые должен уметь обрабатывать множество вещей одновременно. Ну и т.п. Весь десктоп давно многопоточный, даже если всякая школота до сих пор не умеет показать progress bar без того, чтобы завесить весь UI-поток.

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

И первый и второй пункт, безусловно, могут быть в какой-то мере решены на уровне библиотек. Но эти решения будут всегда ограничены возможностями как самого языка, так и возможностями рантайма этого языка. Смешной момент: всякие лямбды, замыкания и прочая функциональщина прекрасно себе решались Boost'ом, но появление всего этого в С++11 было воспринято просто на ура. Но когда заходит речь о многопоточности, например, внезапно общее отношение кажется таким: все решается библиотеками легко и просто нафига это на уровне языка? Понятно, что это относится не только к С++.

Давайте посмотрим, что у нас считается state of the art в некоторых современных языках. Для тех, кто в наглухо забронированном танке. Это то, что доступно в языках на уровне стандартов и стандартных библиотек:

— С++. Стандарт С++11. http://en.cppreference.com/w/cpp/thread Уровень примерно на уровне WinAPI середины 90-х. Потоки, мьютексы. Ну еще есть futures.
— Python. https://docs.python.org/2/library/threading.html#module-threading и https://docs.python.org/3/library/threading.html#module-threading. То же самое. Потоки, мьютексы. Ну или смешные костыли в виде запуска отдельного ОС-процесса
— Java. Возьмем ссылку на википедию. https://en.wikipedia.org/wiki/Java_concurrency Все то же. Потоки и мьютексы. Плюс возможно процессы ОСи
— C#? Objective-C? Ruby? Php бггг? кто там еще есть в индексе? Все то же самое и одно и то же.

(понятно, что я могу ошибаться, или ошибаться в деталях)

Еще раз повторю. State-of-the-art для практически любого современного языка программирования является: создание OC-потоков и процессов, общение между потоками через разделяемую память, мьютексы. Буквально единицы представляют еще и асинхронные вызовы через futures/promises, но и они обычно являются отдельным потоком/процессом в ОСи с общением через разделяемую память.

Еще раз для тех, кто в наглухо запаянном танке: это — state-of-the-art, кодифицированный в стандартах языка и его стандартных библиотеках. О third-party библиотеках я еще не начал говорить.

И это — только самая-самая вершина айсберга.

Вниз по кроличьей норе

Если у тебя однопоточное приложение — проходим мимо. Как только потоков становится N > 1, все становится очень печально (в большинстве языков). Потому что потоками надо управлять и потокам надо общаться друг с другом.

Что такое управлять потоками?

Все просто. Мы запустили поток, нам нужно:
— Знать, что поток выполняется
— Знать, когда поток завершится, и узнать, как поток завершился (вернул значение, вылетел с ошибкой)
— Возможно, перезапустить поток, если он завершился
— Убить поток, если это необходимо

Что такое общаться друг с другом?

Все просто. Часто одному потоку надо знать о промежуточном состоянии другого потока или передать в другой поток какое-то свое промежуточное состояние. Как простейшие примеры:
— UI поток должен узнать, что что-то изменилось в долгоиграющем потоке, чтобы обновить свое состояние (прогрессбар, например)
— Долгоиграющий поток должен узнать, что что-то изменилось в окружающем мире, чтобы продолжить работу (появились новые данные, изменилась конфигурация и т.п.)

Какие инструменты предлагает нам подавляющее большинство современных языков программирования для решения этих двух вопросов? Никакие. Нет таких инструментов. Вплоть до смешного:

There is no portable way in C++11 (that I'm aware of) to non-cooperatively kill a single thread in a multi-thread program (i.e. without killing all threads).


Не удивительно, что при таком сне разума рождаются чудовища:
— люди просто не знают о каких-либо инструментах за пределами условного mutex.lock и не понимают, зачем такие инструменты нужны. Потому что даже фраза «потоки могут общаться друг с другом» являются китайской грамотой для языков, в которых while(shared_variable != true) execute() является единственным способом общения между потоками
— те, которые понимают, всеми силами стараются обойти ограничения языка и рантайма, создавая (иногда десятки) библиотек разной степени «фичастости». В итоге получается как в комиксе xkcd про стандарты. Есть разные библиотеки с пересекающимся функционалом, ни одна из которых не предлагает комплексного подхода к решению.

Э, так чё там Эрланг

Ситуация с Эрлангом — как с Лиспом и всякими ML'ями.



C and ML were both finished in 73. ML had first-class functions, GC, type inference, algebraic data types, pattern matching, and exceptions.


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

Ситуация с Эрлангом на данный момент примерно такая же

http://erlang.org/pipermail/erlang-questions/2012-February/064321.html

Any sufficiently complicated concurrent program in another language contains an ad hoc informally-specified bug-ridden slow implementation of half of Erlang.


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

Ээээ. Так что там Эрланг, я так и не понял

Erlang не появился ниоткуда. Он появился в Ericsson'е для решения проблем в телекоме. Какие это были проблемы? Надо было обрабатывать множество пользовательских запросов одновременно, в пределах заданных параметров, с возможностю прозрачно переносить пользователя с системы на систему в случае сбоев — в том числе и хардварных. То есть, то чем сегодня занимается чуть ли не каждый первый В 80-х такие задачи стояли у достаточно небольшого количества компаний.

Над этой задачей Ericsson работал давно и успешно. В частности, до Erlang'а у Ericsson'а уже был PLEX: special-purpose, pseudo-parallel and event-driven real-time programming language. В 81-м году Ericsson организовал лабораторию, перед которой ставилась задача: "to suggest new architectures, concepts and structures for future".

В 1993-м году заведующий этой лабораторией Bjarne Däcker сформулировал накопившийся опыт построения телекоммуникационных систем так (цитирую по DÄ2000):

1. The system must be able to handle very large numbers of concurrent activities.
2. Actions must be performed at a certain point in time or within a certain time.
3. Systems may be distributed over several computers.
4. The system is used to control hardware.
5. The software systems are very large.
6. The system exhibits complex functionality such as, feature interaction.
7. The systems should be in continuous operation for many years.
8. Software maintenance (reconfiguration, etc) should be performed without stopping the system.
9. There are stringent quality, and reliability requirements.
10. Fault tolerance both to hardware failures, and software errors, must be provided.


(tl;dr по списку: люди, которые говорят, что это — маркетинг буллшит просто не имеют ни малейшего понятия, что они несут)

Механизмы и инструменты которые Erlang предоставляет из коробки, выросли напрямую из необходимости соответствовать этим требованиям. По некоторым пунктам:
1. Требует возможность создать множество параллельных процессов, которыми надо управлять, и которым надо общаться между собой.
2. Требует гарантий на время отклика системы
3. Требует механизмов для распределения системы по нескольким компьютером, что тянет за собой множество требований. Например, если умерла система X, система Y должна иметь возможность гарантированно об этом узнать
5. Нужны из коробки механизмы управления крупными системами (в случае с Эрлангом — управления множеством процессов)
8. Нужно иметь возможность обновить систему, не останавливая ее, что тянет за собой множество требований: как заставить долгоживушие процессы обновить код, что делать со stale code в памяти, как производить апгрейд структур в памяти и т.п.
10. В частности: если процесс умирает, он не должен утянуть за собой всю систему. Если процесс умер на машине X, об этом надо гарантированно узнать на машине Y и т.п.

Остальные пункты могут быть не такие радужные, поэтому я описал выборочно

Все это выливается в требования, сформулированные одним из создателей языка, Вирдингом:

Properties of the Erlang system

— Lightweight, massive concurrency
— Asynchronous communication
— Process isolation
— Error handling
— Continuous evolution of the system
— Soft real-time


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

Но как же библиотеки и Эрланг не нужен?

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

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

Просто создать 100500 потоков недостаточно. Ими надо управлять. Какие из них завершились аварийно? Надо ли их перезапустить? Надо ли их перезапустить, если они завершились не аварийно? Программа получила сигнал QUIT, как сообщить всем потокам, что усё, надо завершаться и подчищать за собой? Некоторые из потоков запускают какие-то дополнительные потоки, как управляются они? Потоки зависят от промежуточных данных других потоков, как организовано взаимодействие этих потоков? И еще несколько десятков таких вопросов.

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

— Библиотека X может создать 100500 потоков, но общение строго через shared state с мьютексами и прочим.
— Библиотека Y может создать 100500 потоков, общение агентами/сообщениями, но нет никакой возможности управлять этими потоками, кроме как врукопашную реализовывать весь мониторинг и прочее
— Библиотека Z, может создать 100500 потоков, общение агентами/сообщениями, есть управление потоками, но нет изоляции потоков, поэтому любое залетное деление на 0 просто убивает всю программу

и так далее и тому подобное.

При этом люди, заявляющие, что «мы создаем 100500 потоков без управления» или «я всего за несколько часов все сделал» лгут. В первую очередь, самим себе. Все там есть. Есть там и управление (пусть даже самое рудиментарное — процесс X следит за завершением процесса Y). И заботливо разбросанные по коду try/catch'и (иначе умрет вся программа или где-то не будут подчищены ресурсы). И «всего несколько часов» приходится тратить каждый раз, когда надо это все реализовать (причем стандартизированного подхода ведь не существует). Ну и т.п.

Вот так выглядит вполне стандартная программа, написанная на Erlang'е: https://youtu.be/lHoWfeNuAN8

Для не-Erlang'иста это — нереализуемо даже в самых страшных (или влажных) снах. Не потому что «это не нужно» ©, а потому что другие языки (и доступные для них библиотеки) не предоставляют ни малейшей поддержки для реализации такого.

Итак, возвращаясь к теме и тезису топика:

Итак, что там у С++ за траблы с многопоточной невнятностью? И какие языки размалывают его по внятности?


По внятности всех размалывает Erlang. Потому что он из коробки предоставляет удобные, мощные, единообразные и предсказуемые инструменты для создания многопоточных приложений. Не удивительно, что эти инструменты медленно, но верно проникают в другие языки и библиотеки других языков. Потому что многопоточность и распределенность — это не сказки для подрастающего поколения времен DOS'а, а суровая реальность, и с убогими thread.create и mutex.lock в ней не выжить. Но еще некоторое время себя можно обманывать, что «подходы Erlang'а не нужны» и т.п. neFormal все парвильно сказал достаточно рано во всей этой дискуссии
Автор: neFormal
Дата: 05.06.15
:

S>Ну и если уж зашла речь о том, что могут другие языки, то не так уже и мало.

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


и потом это подхватил meadow_meal в этой подветке
Автор: meadow_meal
Дата: 08.06.15
.

К сожалению, множество нечистоплотных людей решило, что они не будут стараться понимать написанное, и приложило множество усилий к тому, чтобы обсуждать все что угодно, только не топик. Оставим это на их совести. В топике было множество попыток от neFormal'а и meadow_meal'а вернуть обсуждение на нужные рельсы. Кто хотел понять, понял. Кто не хотел — на них не обижаются.

Dixi


dmitriid.comGitHubLinkedIn
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.