Re[15]: Почему Эрланг
От: Ночной Смотрящий Россия  
Дата: 12.06.15 16:17
Оценка:
Здравствуйте, vdimas, Вы писали:

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


Ты же про C#? Так там нет никакой поддержки вложенных короутинов, ни для yield (что очень печально), ни для async. Каждый async метод или итератор создает свой собственный независимый автомат.
Re[35]: Почему Эрланг
От: neFormal Россия  
Дата: 12.06.15 17:02
Оценка: :)
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Чего то не очень заметно, если честно.


спасибо, до свидания. мне эти перепалки ни к чему.
...coding for chaos...
Re[16]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 19:07
Оценка:
Здравствуйте, so5team, Вы писали:

V>>Сорри, а можно ссылку на Asynca?..

S>Вероятно, речь идет о Synca, разрабатывает которую спец из Яндекса, Григорий Демченко.

Посмотрел, улыбнули некоторые вещи:

typedef std::function<void ()> Handler;

struct GC
{
    ~GC();
    
    template<typename T>
    T* add(T* t)
    {
        deleters.emplace_back([t] { delete t; });
        return t;
    }
    
private:
    std::vector<Handler> deleters;
};


На лямбду [t] {delete t;} при создании std::function<> на куче создаётся копия этой лямбды.
Т.е., экономя на немедленном освобождении памяти мы выделяем память. ))

Ну и далее как не улыбнуться выбору имени gcnew.
GC& gc();

template<typename T, typename... V>
T* gcnew(V&&... v)
{
    return gc().add(new T(std::forward(v)...));
}


Ничего, что это торчит из include и ничего, что CLR-проекты часто собирают как дотнетный wrapper над имеющимся С++ кодом, где gcnew будет уже зарезервированным оператором и амба, собсно.

Ну и, его очередь на удаление могла быть куда более простой в плане происходящего:
template<typename T>
void defaultDeleter(void * obj) { delete reinterpret_cast<T*>(obj); }

struct DeleterClosure {
  typedef void DeleterFunc(void * obj);
  DeleterFunc deleter;
  void * obj;
  void operator()() { deleter(obj); }
};

template<typename T>
inline DeleterClosure makeDeleter(T * obj) {
  DeleterClosure dc = { obj, &defaultDeleter<T> };
  return dc;
}

template<typename T>
T* GC::add(T* obj)
{
    deleters.push_back(makeDeleter(obj));
    return obj;
}


И никакие emplace_back не нужны, т.к. перемещать нечего — просто копируется пара указателей.
Re[16]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 19:09
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Ужос, а еще недавно ты говорил "Но уже есть готовые реализации. Дело в их популяризации." А теперь уже "пусть компилятор разгребается"


Конечно ужос, путать stackless-корутину со stackfull. Для второй давно всё есть и в ней можно делать yield на любом уровне вложенности.

===========
Не надоело газировать водоёмы? ))
Re[16]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 20:35
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Ты же про C#? Так там нет никакой поддержки вложенных короутинов, ни для yield (что очень печально)


для yield нет, конечно

НС>ни для async.


для await ты хотел сказать?

НС>Каждый async метод или итератор создает свой собственный независимый автомат.


Есно, речь шла о том, насколько автоматически итерируются вложенные автоматы. Для await — всё ОК, для yield — никакой автоматики, только ручками.

Пример для сравнения на async:
        private static Task t1 = new Task(() => {});
        private static Task t2 = new Task(() => {});

        static async Task Bar() {
            await t1;
        }

        static async Task Foo() {
            await Bar(); 
            await t2;
        }

        static void Main() {
            var task = Foo();
            ThreadPool.QueueUserWorkItem(dummy => t1.RunSynchronously());
            ThreadPool.QueueUserWorkItem(dummy => t2.RunSynchronously());
            task.Wait();
        }


и на yield:
        private static Task t1 = new Task(() => {});
        private static Task t2 = new Task(() => {});

        static IEnumerable<Task> Bar()
        {
            foreach (var task in Coroutine.Await(t1))
                yield return task;
        }

        static IEnumerable<Task> Foo()
        {
            // ручная итерация вложенного метода
            foreach (var task in Coroutine.Await(Bar())) 
                yield return task;

            foreach (var task in Coroutine.Await(t2))
                yield return task;
        }

        static void Main1()
        {
            var task = Coroutine.Wait(Foo());
            ThreadPool.QueueUserWorkItem(dummy => t1.RunSynchronously());
            ThreadPool.QueueUserWorkItem(dummy => t2.RunSynchronously());
            task.Wait();
        }

        // хелпер для IEnumerable-корутин
        static class Coroutine { ... }
Отредактировано 12.06.2015 20:39 vdimas . Предыдущая версия .
Re[17]: Почему Эрланг
От: so5team https://stiffstream.com
Дата: 12.06.15 21:33
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Ничего, что это торчит из include и ничего, что CLR-проекты часто собирают как дотнетный wrapper над имеющимся С++ кодом, где gcnew будет уже зарезервированным оператором и амба, собсно.


Полагаю, что в Яндексе проблемы CLR не интересны. Не удивлюсь, если у MSVC++ вообще будут проблемы при сборке Synca.

В связи с качеством поддержки C++11 в MSVC++ многие современные проекты на работоспособность под Windows вообще не заморачиваются, ограничиваясь платформами, где есть gcc и clang.
Re[16]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 22:44
Оценка:
Здравствуйте, Ikemefula, Вы писали:

I>Вот аналог бесконечного цикла, но асинхронного

I>
I>// опаньки - однопоточный JS крутит N бесконечных задач одновременно
I>


Увы, увы, неодновременно. ))
Каждый раз — целиком очередную задачу и не приступит к следующей до тех пор, пока не выполнена текущая.


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


Кооперативно — это значит поочередно.


I>Это ровно то же, что и зеленые потоки


Если M зеленых потоков сидят на N потоках вытесняющей ОС, где N>1, то это не одно и тоже. Это сразу же другая вселенная, даже если N больше 1-го на жалкую единицу. ))


I>Теперь сложнее, вводим глобальное состояние


ОМГ))
Не надо ничего дополнительно вводить, в жабаскрипте оно и так есть, даже если у тебя нет ни одной глобальной переменной, а только ф-ии.

I>
I>//опаньки - порядок доступа к глобальному состоянию __недетерминирован__
I>


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


I>Вот тебе и монады и проблемы с глобальным состоянием в однопоточном и, внимание, МНОГОЗАДАЧНОМ коде.


Ты уж определись, однопоточном или многозадачном. ))
У тебя код не однопоточный, а многопоточный. Способ организации многопоточности — кооперативная многозадачность. Причем, только с версии жабаскрипта, где появился async/await. До этого были либы promise/deferred разной степени неудобства, где тебе приходилось ручками "склеивать" логику на deferred.then(nextTask) и код был СТРОГО однопоточный и однозадачный.


I>>>Отсюда получаем тот самый "недетерминированый порядок"

V>>Я вот уже близок, чтобы повторить тебе всё то, что было в той эпической дискуссии, на которую ты, на свою беду, постоянно ссылаешься. ))
I>Нет аргумента. Если есть чего сказать — скажи это кодом.

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

V>>Тем не менее, нам нужна лишь изоляция от конкурирующего доступа, т.е. изоляция от тех самых гонок. Если гонок нет, то "изоляция" соблюдается или нет?


I>Ты получил ответ, что же такое гонки ?


Да.

I>Ты согласен с формулировкой ?


Да.

I>Ты видишь потенциальные гонки в моем коде ?


Нет.
Пипец! Это уже не просто жесть, а дурдом.
Но мне уже любопытно, сможешь ли ты на ЭТОТ раз одолеть сие затруднение.

Понимаешь что хреново-то?
Ты даешь формулировку, даешь её правильно, иногда начинает казаться, что ты всё понял... ан нет!!!
Ты их зубришь, что ле, эти формулировки, или как?

Приведу простой пример детерминированности/недетерминированности происходящего.
Простой инкремент глобальной переменной:
function increment() {
  i++;
}

Давай над ним помедитируем. Что здесь происходит?
Происходит чтение значения переменной по символьному адресу 'i', инкремент и запись нового значения обратно по адресу 'i'.
В отсутствии гонок у тебя всегда будет детерминированная последовательность: прочитал -> модифицировал -> записал. Соответственно, значение i всегда будет детерминированно — оно будет на единицу больше предыдущего. Ты можешь в следующей строчке прочитать это значение и убедиться. Гонок не будет и ты можешь смело пользовать знание об отсутствии гонок в своём коде, т.е. твой код может расчитывать, что предыдущая операция была детерминирована, т.е. ты можешь смело расчитывать на значение i в следующей же строчке за вызовом increment().

Что происходит, если бы были гонки? Например, пусть это не жабаскрипт, а С++:
void increment() {
  i++;
}

thread t1(increment);
thread t2(increment);

В этом случае, ты не можешь быть уверенным в значении переменной 'i' после инкремента, т.е. её значение недетерминировано, потому что был нарушен порядок прочитал -> модифицировал -> записал. Другой поток мог вклиниться, могло произойти одно из 2-х возможных для этой ситуации взаимных наложений стадий, в итоге конечный результат операции будет неизвестен в каждом из потоков.

В этом месте преподаватель обычно смотрит в глаза студентов и пытается найти там проблески сознания. ))
Ну реально, у нас даже троешники понимали эти вещи к 3-му курсу.


V>>Это лишь точка зрения на происходящее.

I>Покажи, как 'разрезать' задачу, которая есть N некоторых событий от пользователя. По моему — эвенты надо связывать.

Это зависит от "парадигмы", то бишь, от "взгляда на проблему". Как бы цинично это не звучало — но это именно так.

Например, представь линейную логику обработки событий с клавиатуры именно как линейную логику:
// запусти этот процесс через setTimeout
async function keyboardProcessor()
{
  showInvitation("Press 'a'");
  var key = await readKeyboard();

  while(key != 'a') {
    showInvitation("What a silly boy! Press 'a' please!");
    key = await readKeyboard();
  }

  showInvitation("Press 'b'");
  var key = await readKeyboard();
  ...
}


Представь, что источники событий — это другой/другие процессы. Забудь на минуту, что в браузере эти "процессы" выполняются в одном потоке ОС. С точки зрения твоей программы — это другие процессы.

Далее представь, что м/у этими процессами есть некая очередь событий, ведь именно так общаются процессы во "взрослом" мире:
var keyboardAwaiter = null;
var keyboardQueue = new Queue();

function readKeyboard() {
  if(keyboardQueue.length() != 0)
    return Promise.resolve(keyboardQueue.take());

  return new Promise(
    function(resolve, reject){ 
      keyboardAwaiter = resolve;
    });
}

// подпишись сюда в браузере на событие с клавиатуры
function onKeyboard(nextChar)
{
  keyboardQueue.push(nextChar);

  if(keyboardAwaiter != null) {
    var awaiter = keyboardAwaiter;
    keyboardAwaiter = null;
    awaiter(keyboardQueue.take());
  }
}


А теперь вишенка на тортЕ!
Есть две глобальные переменные: keyboardAwaiter и keyboardQueue. Доступ к ним происходит из РАЗНЫХ логических потоков. Но нет НИКАКИХ мьютексов/критических_секций, защищающих эти переменные! Му-а-ха-ха, как грится.


V>>Чтобы одновременно выполнять некий участок кода, нам надо будет явным образом создать еще один логический поток уровня ОС. ))

I>Неверно. ОС ничего не знает про зеленые потоки, а они, между прочим, выполняются по правилам кооперативной многозадачности. Логически — одновременно.

Поочередно, бро. Только поочередно. Если выполняется onKeyboard, то это означает, что не выполняется ни readKeyboard, ни keyboardProcessor.

Жабаскрипт он такой, пока весь код не выполнит, не угомонится, падла! )))
Поэтому глючный жабасрипт подвешивает ГУЙ браузера.

К твоему жабаскрипту есть очередь событий и он эту очередь тупо разгребает на своих колбэках. Это просто внутренняя механика: ReadMessage, TransalateMessage, DispatchMessage. ))
Постановка задачи в очередь на исполнение происходит через гуишный таймер window.setTimeout(func, timeout).

Когда ты выполняешь awaiter(keyboardQueue.take()), ты ставишь в очередь сообщение: "выполни-ка мне скрипт, связанный с продолжением awaiter). Когда жабаскрипт разгребет всю текущую очередь событий и, наконец, дойдет очередь и до этого сообщения, то будет вызван re-enter в keyboardProcessor, в одну из строчек await readKeyboard().


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

I>После того, как задача разделена на части, её нужно правильно связать, для обеспечения корректного control flow.

Это если язык не поддерживает синтаксисом сию задачу, то да. В жабаскрипте связывали ручками через .then().


V>>Мне повторять пример из той давней дискуссии про то, как в случае такой кооперативной многозадачности мы порой можем обходиться без обкладывания кода критическими секциями? Или ты уже догадался, когда такое возможно?

I>Ключевое слово — 'порой'. Я привел пример, где это невозможно.

Ты ошибся. Именно в жабаскрипте это ВСЕГДА возможно. Т.е. не надо ни одну переменную обкладывать мьютексами.

V>>Ну вот включи воображение, представь, что у тебя

I>Я привел два примера кода. Ты пока даже не понял, где там логические потоки, кооперативная многозадачность и откуда берется недетерминированый порядок выполнения задач.

Да всё я понял, только тебе не понравится, что я понял. Похоже, ты был не в курсе, как браузер исполняет жабаскрипт.
Твой node.js исполняет скрипты аналогично браузерной жабе, с той разницей, что вместо гуевой очереди сообщений используется какая-нить другая очередь. Но происходящее в точности эквивалентно — каждый запуск скрипта должен отработать до конца, только потом будет запущена следующая задача из очереди к текущему контексту.


V>>Т.е., в твоём жабаскрипте на любой вызов любой ф-ии или лямбды всегда неявно протаскивается некий "контекст". Эти контексты связаны друг с другом по цепочке вызовов и образуют т.н. связанную "область видимости" глобальных переменных и ф-ий (которые в жабаскрипте и в лиспе — тоже лишь "переменные", хранящие указатель на тело ф-ии). Т.е. фиг с ними, с переменными, но, блин, когда ф-ия представлена переменной — тот тут надо быть оч осторожным, не? ))


I>Не имеет значения, функция, переменная или база данных. Главное что есть недетерминированый порядок выполнения.


Детерминирован. Именно потому, что ф-ия — это переменная, это, блин, ячейка памяти, а не константный символ, как в компиллируемых языках.
Тут надо понимать всю эту засаду интерпретаторов в плане многопоточности.


V>>Собсно, чем мне не нравится жабасрипт — это тем, что без навыков отслеживать цепочку хотя бы из 3-х контекстов ты эффективно программировать не будешь. Именно поэтому жабаскрипт пестрит вот таким высмеянным сто раз синтаксисом:

V>>
V>>    })
V>>   })
V>>  })
V>> })
V>>

I>Смотри внимательно мои примеры кода — это, внимание, асинхронный JS ! И там нет того, о чем ты говоришь.

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


V>>Потому что когда скрипт выполняет код, даже просто ОБЪЯВЛЯЮЩИЙ некую глобальную ф-ию

I>Давай представим, для простоты, что глобальный ресурс это файл.

Неинтересно. Файл синхронизируется как ОС, так и низлежащей библиотекой. Ты на нем не получишь гонок ни в жисть — все обращения к одному и тому же файлу синхронизируются.

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


I>Вобщем, все что не по теме, я скипнул.


Та ради бога.
Только странно, что ты сначала долго и нудно провоцируешь на значительный обмен инфой, т.е. не в рамках привычного "сам дурак", а потом, когда обсуждение пытается перерасти в разряд адекватных, сам же часто склонен съезжать на "сам дурак". ))
Ты уж определись, тебе шашечки или ехать.
Re[18]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 23:04
Оценка:
Здравствуйте, so5team, Вы писали:

S>В связи с качеством поддержки C++11 в MSVC++ многие современные проекты на работоспособность под Windows вообще не заморачиваются, ограничиваясь платформами, где есть gcc и clang.


Это немного не так.
Во "взрослом мире" полно серваков даже еще на RHEL 6.0 с каким-нить gcc 4.4.3, который уступает в степени поддержки C++11 в MSVC, на котором, как ни странно, можно собрать программы, работающие в более ранних версиях Windows. В Linux же такой трюк не сработает, увы. ))

Ну и вообще, степень поддержки gcc С++11 тоже не блещет — баги даже в condition. Из всех имеющихся на сегодня компиляторов только компилятор от VS2013 содержит более-менее безбаговую реализацию стандарта С++11. У остальных пока есть известные баги.
Re[18]: Почему Эрланг
От: vdimas Россия  
Дата: 12.06.15 23:07
Оценка:
Здравствуйте, so5team, Вы писали:

S>Полагаю, что в Яндексе проблемы CLR не интересны.


Ну и даже фиг с ним, с CLR.
Вот этот момент

Т.е., экономя на немедленном освобождении памяти мы выделяем память. ))

говорит сразу о многом. Да практически обо всём. ))
Я прошелся по этой либе... собсно, подобное везде там.
В нашей конторе эта либа не прокатила бы ни по кач-ву проработки публичного АПИ, ни по кач-ву реализации.
Re[9]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 06:24
Оценка:
Здравствуйте, so5team, Вы писали:

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


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

S>>Совершенно верно: вместо потоков ОС нужно использовать другие механизмы шедулинга, т.е. велосипедить свою реализацию подмножества Erlang-а, т.к. встроенные механизмы шедулинга не предназначены для высоких нагрузок.


S>Покажите, пожалуйста, что для этих целей предназначен Erlang.

S>А то вот буквально рядом
Автор: kostik78
Дата: 09.06.15
свидетели Erlang-а рассказывают, что большие объемы данных обрабатываются не без помощи написанных на plan-C нод. А ведь нода -- это отнюдь не мелкая NIF-ка.


Большие объёмы данных, много параллельных запросов и плотные потоки — это три разных случая, обычно не сильно пересекающиеся. Свидетель описывает случай 3, который на штатном рантайме, считаем, нереализуем. Случай 2 — самый подходящий. 1 — зависит от подробностей.
The God is real, unless declared integer.
Re[13]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 06:39
Оценка:
Здравствуйте, so5team, Вы писали:

S> Соответственно задачей разработчика является маппинг M короутин на N нитей ОС. Соответственно, если где-то программист накосячил и напрямую вызвал блокирующую операцию внутри короутины, то заблокируется одна из выделенных под обслуживание короутин нитей. Из-за этого работа с короутинами требует большего внимания, т.к., в отличии от вытесняющей многозадачности, которую может обеспечить нитям ОС, с короутинами приходится работать в кооперативном режиме. (Вообще-то неправильно отождествлять короутины с легковесными потоками, но в контексте данного разговора сойдет).


В FreeBSD (5-6) реализовывали механизм под названием scheduler activations. Процесс, у которого оно включено, при уходе нити в спячку в ядре получает так называемый upcall (разновидность сигнала), который должен отрабатываться как признак того, что LWP ушёл в сон и надо переключить на другой LWP. Это автоматически решало проблему с детектом блокировки.
Но к 7.0 от этого отказались, потому что ресурсов не хватило отладить, а 1:1 модель реализовывалась легче. А жаль — распространить этот механизм на все основные ОС дало бы хорошую возможность гарантировать наличие безопасных в плане блокировок LWP для рантайма.

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

S>Процессы в Erlang-е -- это более продвинутая разновидность легковесных потоков. ОС про них ничего не знает, представление о них имеет только VM Erlang-а. Но, т.к. Erlang-овская VM сама выделяет кванты времени своим процессам, она имеет возможность вытеснять процесс после определенного количества редукций (т.е. выполненых инструкций VM). Кроме того, стандартная библиотека Erlang-а позволяет Erlang-овскому шедулеру снимать процесс при обращении к блокирующим операциям, передавать саму операцию на соответствующий рабочий поток, а затем поднимать задачу при получении ответа. Т.е. получается своя мини-ОС, которая работает хорошо только, если Erlang-овый код не использует обращения к NIF-ам (т.е. функциям, написанным на C).


А это уже недостаток текущего интерфейса NIF. Сделать в нём требование переключения (с последующей активацией из таймера или соседней нити ОС) было бы относительно просто, но усложняет понимание шедулера.

S>Проблема M:N возникает, когда у нас много однотипных задач, которые, преимущественно, требуют только процессора. Например, выполняют вычисления. Это означает, что каждая задача, получив свой рабочий квант, будет использовать его полностью, не давая шедулеру снять задачу и отдать процессор кому-то еще. Тут чем быстрее работает задача, чем большие кванты времени ей выдаются, тем лучше. Проблема Erlang-а по сравнению с нативным кодом здесь в том, что Erlang медленный.


Ну cloudozer'овцы говорят, что они достигли на некоторых примерах, что Erlang'овский код, переделанный через LLVM, в три раза быстрее C++ного — за счёт способствующей этому аллокации регистров VM. Ванильная VM, конечно, такого не даст, и HIPE тоже мало чем способствует.
The God is real, unless declared integer.
Re[14]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 06:42
Оценка:
Здравствуйте, meadow_meal, Вы писали:

_>В-третьих, всегда можно поменять ниф на драйвер и использовать driver_async — раз уж упомянули о выносе блокирующего IO в async thread, никто не мешает пользоваться ими для пользовательских функций.


И оно будет возвращать нотификацию сообщением в единственную входную очередь процесса? Как-то некрасиво.

_>В конце концов, есть и просто enif_thread_create/erl_drv_create_thread.


Так всё равно та же проблема. Одна очередь и нет внеочередных методов пробуждения.
The God is real, unless declared integer.
Re[29]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 06:47
Оценка:
Здравствуйте, Ночной Смотрящий, Вы писали:

НС>Но ведь С++ то не единственная альтернатива. Java или дотнет и компилируются секунды, и прекрасно позволяют перезагружать классы/сборки на ходу.


Если за последние пару лет ничего не изменилось, они не позволяют плавный переход на новый код, что означает 1) отсутствие требования синхронности перехода для всех объектов, 2) вообще сохранение объектов данного типа (без сериализации и десериализации состояния).
The God is real, unless declared integer.
Re[3]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 07:25
Оценка:
Здравствуйте, Sharov, Вы писали:

S>И да, раз уж Эрланг так крут, чего же гугл стал изобретать свой велосипед Go, а не форкнул Эрланг.


Ну изобретение Go вообще было осмысленно только показом миру, как нельзя делать.

S>Эрланг это язык, конкретно заточенный под телеком, со своими минусами и плюсами. Зачем его всюду пихать?


Затем, что такого "телекома" стало в сотни раз больше, чем было на его старте.

S>Это исключительно нишевый язык.


Зато ниша очень разрослась.
The God is real, unless declared integer.
Re[5]: плюс про динамику Эрланга
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 07:33
Оценка: :))
Здравствуйте, neFormal, Вы писали:

DE>>Не уверен, что это показатель чего-либо. У динамических языков есть свои поклонники, логично, что они пишут, в основном, именно на таких языках (ну и наоборот). Правильнее было бы узнать нужны в сложившейся инфраструктуре преимущества именно динамических языков, а то может оказаться, что оно так по историческим причинам.


F>но эрланг же выбирают не потому, что он динамический.


В существенной мере и поэтому.

Потому, что любой интерфейс можно расширить, например, через параметр options/flags/whatever, передавая его как proplist каких-то уточнений с любыми типами данных (на статически типизированных языках это потребует извращений вроде Variant, ещё и с контролем владения — в общем, то же самое, но вручную). Проверка же наличия какой-то опции на списках короче десятков элементов очень дешёвая — дешевле самого вызова функции.

Потому что любой возврат результата типа {error, Error}, где эту самую Error надо записать в лог и в отдельных случаях проанализировать на частный набор известных случаев — не требует никакой особой поддержки никакого типа этого Error, не надо изобретать сначала базовый класс, потом подкласс для частного случая, его идентификацию, передачи через хедера...

Если программа отлажена, размечена spec'ами функций и прошла dialyzer — все места, где осталось явное any() или аналог, это места, где по сути задачи не может быть чётких типов, и формализация на данном этапе невозможна. И только начиная с этого момента (не раньше) можно тосковать о том, что какое-нибудь сложение вместо одной машинной команды превращается в цепочку действий VM.
The God is real, unless declared integer.
Re[10]: Почему Эрланг
От: so5team https://stiffstream.com
Дата: 13.06.15 07:35
Оценка:
Здравствуйте, netch80, Вы писали:

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


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


О том и речь. Возможность задешево породить в Erlang-е огромное количество легковесных процессов не обязательно будет востребована всегда. На каких-то задачах достаточно будет иметь всего несколько рабочих процессов, которые и будут делать все вычисления/всю обработку. Но тогда подход Erlang-а будет не сильно отличаться от подхода C++/Java.

N>Большие объёмы данных, много параллельных запросов и плотные потоки — это три разных случая, обычно не сильно пересекающиеся. Свидетель описывает случай 3, который на штатном рантайме, считаем, нереализуем. Случай 2 — самый подходящий. 1 — зависит от подробностей.


Отрадно, когда в треде появляются люди, понимающие что к чему

ИМХО, со вторым случаем, т.е. с потоком параллельных запросов, так же не все однозначно. Можно создавать по воркеру на каждый запрос. А можно иметь отдельный воркер на ядро, а запросы представлять внутри воркера в виде конечных автоматов. И у того, и у другого подхода есть свои достоинства и недостатки.
Re[3]: плюс про динамику Эрланга
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 07:35
Оценка: :)
Здравствуйте, Mamut, Вы писали:

M>В конце 90-х в офисе Эрикссона почти в полном составе сидела команда, создавшая GHC. Они полтора года пытались придумать для Эрланга систему типов. Не смогли Некоторые свойства системы (типа hot code loading) этому очень мешают.


Им ничто не мешало формализовать типы, например, для арифметики и обеспечить JIT для них.
Если не сделали, то только потому, что это не считалось жизненно важным для его ниши.
The God is real, unless declared integer.
Re[19]: Почему Эрланг
От: so5team https://stiffstream.com
Дата: 13.06.15 07:37
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Я прошелся по этой либе... собсно, подобное везде там.


Как я понимаю, эта либа была написана одним разработчиком, в свободное время и под себя. Соответственно, пространство для улучшений там огромное.
Ну а разработчик Synca более чем вменяемый, думаю, он заинтересуется возможностью улучшить качество своего инструмента.
Re[17]: Почему Эрланг
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.06.15 07:38
Оценка:
Здравствуйте, vdimas, Вы писали:

Я оставил самый минимум.

Кстати, вопрос такой — ты в курсе, что Windows до определенной версии поддерживала только кооперетивную многозадачность ?
Ты в курсе, что уже тогда в ней были и семафоры, и муткесы.
Как так, ведь в кооперативной многозадачности "гонок нет"
Ась ?


V>Нет.

V>Пипец! Это уже не просто жесть, а дурдом.
V>Но мне уже любопытно, сможешь ли ты на ЭТОТ раз одолеть сие затруднение.

Все очень просто — ты хочешь съехать с кооперативной многозадачности.

V>Что происходит, если бы были гонки? Например, пусть это не жабаскрипт, а С++:

V>
V>void increment() {
V>  i++;
V>}

V>thread t1(increment);
V>thread t2(increment);
V>

V>В этом случае, ты не можешь быть уверенным в значении переменной 'i' после инкремента, т.е. её значение недетерминировано,

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

У тебя плохой пример. Раздели чтение переменной и запись на две операции. Внезапно, обнаруживаем инвариант — поток затирает ровно то значение, которое перед этим прочёл.
Если компилятор не гарантирует атомарность инкремента, многозадачный код ломает этот инвариант.
Отсюда ясно, что не состояние неопределено, а порядок выполнения недетерминирован, ибо может быть реально так:

thread1 read i
thread2 read i
thread1 write i + 1
thread2 write i + 1

И именно эта последовательность ломает состояние. То есть, гонки налицо. Видишь их ?

Теперь твой пример на JS, вместо потока — задача. Что читаем i и записываем отдельно.

var i = 0;
var actual = 0;
function read_i(clb) {
    setTimeout(function() { clb(i); }, 1000);
}
function write_i(newI, clb) {
    setTimeout(function() { i = newI; clb(); }, 1000);
}

function task() {
    console.assert(i == actual++);
    console.log('before read', i);
    read_i(function(value) {
         console.log('after read', i);
         write_i(value + 1, task);
         console.log('after write', i);    
    });
}


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

task 1 read i
task 2 read i
task 1 write i + 1
task 2 write i + 1

Сравниваем, с тем, что было в вытесняющей многозадачности

thread1 read i
thread2 read i
thread1 write i + 1
thread2 write i + 1


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

Что характерно, никакие await/yield тебе не помогут. Решить проблему может только своя собственная очередь операций, что почти уже мутекс или семафор, только для кооперативной многозадачности


V>А теперь вишенка на тортЕ!

V>Есть две глобальные переменные: keyboardAwaiter и keyboardQueue. Доступ к ним происходит из РАЗНЫХ логических потоков. Но нет НИКАКИХ мьютексов/критических_секций, защищающих эти переменные! Му-а-ха-ха, как грится.

Сначала ты связал разные задачи, что бы выполнить их в одной задаче. И сделал ты это через await.

I>>Неверно. ОС ничего не знает про зеленые потоки, а они, между прочим, выполняются по правилам кооперативной многозадачности. Логически — одновременно.


V>Поочередно, бро. Только поочередно. Если выполняется onKeyboard, то это означает, что не выполняется ни readKeyboard, ни keyboardProcessor.


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

V>Жабаскрипт он такой, пока весь код не выполнит, не угомонится, падла! )))

V>Поэтому глючный жабасрипт подвешивает ГУЙ браузера.

Код на любом языке может подвешивать любую программу.

I>>После того, как задача разделена на части, её нужно правильно связать, для обеспечения корректного control flow.

V>Это если язык не поддерживает синтаксисом сию задачу, то да. В жабаскрипте связывали ручками через .then().

I>>Ключевое слово — 'порой'. Я привел пример, где это невозможно.


V>Ты ошибся. Именно в жабаскрипте это ВСЕГДА возможно. Т.е. не надо ни одну переменную обкладывать мьютексами.


И это неверно. В моем примере ломается инвариант если запустить больше одной логической задачи.

I>>Не имеет значения, функция, переменная или база данных. Главное что есть недетерминированый порядок выполнения.


V>Детерминирован. Именно потому, что ф-ия — это переменная, это, блин, ячейка памяти, а не константный символ, как в компиллируемых языках.


V>Я тебе тоже привел примеры без этого — но это лишь примеры. Во взрослой жизни приходится каждую ф-ию оборачивать как лямбду, чтобы она не попадала в текущий контекст.




I>>Давай представим, для простоты, что глобальный ресурс это файл.


V>Неинтересно. Файл синхронизируется как ОС, так и низлежащей библиотекой. Ты на нем не получишь гонок ни в жисть — все обращения к одному и тому же файлу синхронизируются.


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

Это, в частности, проявляется в том, что содержимое файла при одном запуске будет "111222" а во втором случае "121212" а в третьем "122211"
Re[11]: Почему Эрланг
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 13.06.15 08:16
Оценка:
Здравствуйте, so5team, Вы писали:

S>О том и речь. Возможность задешево породить в Erlang-е огромное количество легковесных процессов не обязательно будет востребована всегда. На каких-то задачах достаточно будет иметь всего несколько рабочих процессов, которые и будут делать все вычисления/всю обработку. Но тогда подход Erlang-а будет не сильно отличаться от подхода C++/Java.


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

1. Если дизайн предполагает штатной ситуацией наличие больше 10 (условно) сообщений во входной очереди процесса, архитектура построена неправильно. Все асинхронные (то есть следующее шлётся не дожидаясь ответа на предыдущее) потоки должны быть перенесены внутри ноды в специальные драйвера, которые поставляют процессу данные по методу, аналогичному {active,false|once|N} для gen_tcp. Все асинхронные потоки между нодами в кластере должны идти вне штатных межнодовых коннекторов.

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

Дидактически отличный пример, где это реализуется само без участия программиста — отдача по tcp; пока драйвер не сложит порцию в сокетный буфер, gen_tcp:send не получит подтверждения, то есть OTP здесь принудительно обеспечивает синхронность. Именно поэтому Лапшин почти не боролся с тем, что нам убивало системы

2. Если долгоживущих процессов заметно больше количества ядер, архитектура построена неправильно. Допускается постоянно живущих в объёме M*Ncores, где M — достаточно малая константа. Допускается огромное количество (грубо говоря, миллионы) процессов, но чтобы каждый из них жил одну операцию (самое крупное — ответ на http запрос, или транзакция), не постоянно, и чем короче — тем лучше (нижняя граница срока жизни — определяется моментом, когда переброс данных между такими эфемерами становится слишком дорогим). При требовании долгоживущего состояния — делать пулы рабочих процессов, каждый из которых держит комплект состояний.

Нарушение этого пункта убивает производительность на корню за счёт раздувания памяти и не приводимого в адекват в этом случае GC, в тяжёлых случаях это убивает ноду за счёт переполнения по памяти (с последствиями, аналогичными пункту 1).

3. Если действия, неэффективные для рантайма с динамическими типами и постоянной их проверкой (грубо говоря, математика). Выносить в нативный код или пользоваться новомодными фишками типа транслятора через LLVM (cloudozer сейчас такое творит).

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

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

1. Разделение активности по процессам, которые видны рантайму и поэтому контролируемы. Для каждого процесса можно узнать подробно, чем занимается (получить stacktrace, который не будет съеден оптимизациями; получить словарь процесса, куда часто экспортируется статистика и прочие подробности состояния; узнать объём очереди сообщений; и т.п.)

2. За счёт обмена сообщениями вместо блокируемого состояния — нет проблемы от грубого вмешательства, такого, как посылка фиктивного ответа; убийство процесса-исполнителя целиком (для синхронного запроса на манер gen_call, за счёт монитора таргета со стороны клиента, это немедленно приводит к ответу). Средства контроля и такого защитного управления могут быть как автоматическими, так и ручными, без необходимости глубинного знания потрохов. Там, где в C++/Java/etc. ты не можешь подобраться так, чтобы снять мьютекс, исправив данные, тем самым разрулив заклин на ходу — тут всё это из коробки.

3. Опять же, из коробки кластеризация позволяет, даже не распределяя реальную работу, забраться в живую систему (имея права) через remote shell и починить на ходу. Для автоматов вместо remote shell используется штатный RPC. Заведение этих средств в работу стоит единицы минут времени.

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

N>>Большие объёмы данных, много параллельных запросов и плотные потоки — это три разных случая, обычно не сильно пересекающиеся. Свидетель описывает случай 3, который на штатном рантайме, считаем, нереализуем. Случай 2 — самый подходящий. 1 — зависит от подробностей.

S>Отрадно, когда в треде появляются люди, понимающие что к чему
S>ИМХО, со вторым случаем, т.е. с потоком параллельных запросов, так же не все однозначно. Можно создавать по воркеру на каждый запрос. А можно иметь отдельный воркер на ядро, а запросы представлять внутри воркера в виде конечных автоматов. И у того, и у другого подхода есть свои достоинства и недостатки.

В случае Erlang обычно достаточно по воркеру на запрос. Это проще всем, потому что такой воркер отработал и застрелился, не требуется никакой сложной зачистки за ним. Если же это выводить в пул автоматов, как минимум уже утяжеление, что GC должен реально отработать, а не прихлопнуть целиком всю память застрелившегося процесса. Ну и сами автоматы писать достаточно громоздко. Нам в Clustrx пришлось идти по пути автоматов, потому что надо было хранить состояние объекта со множеством подробностей между оповещениями от него, и я помню неудобства, связанные с этим.
The God is real, unless declared integer.
erlang
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.