Эрланг использует модель процесса, в которой процесс может выполняться на одной и той же виртуальной машине или на другом процессоре. Это возможно только потому, что сообщения копируются между процессами, нет общего (изменяемого) состояния. Многопроцессорный паралелизм идет намного дальше, чем многопоточность, поскольку потоки зависят от общей памяти, это может быть только 8 потоков, работающих параллельно на 8-ядерном процессоре, в то время как многопроцессорность может масштабироваться до тысяч параллельных процессов.

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

Затем я недавно посетил презентацию Кевина Смита «Основы Эрланга» наCodemash.

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

Но я читал, что Erlang используется в приложениях с высокой степенью масштабируемости (именно поэтому Эрикссон создал его в первую очередь). Как может быть эффективно обрабатывать тысячи запросов в секунду, если все обрабатывается как синхронно обрабатываемое сообщение? Разве не поэтому мы начали двигаться к асинхронной обработке - чтобы мы могли воспользоваться одновременным выполнением нескольких потоков и достичь масштабируемости? Кажется, что эта архитектура, хотя и безопаснее, является шагом назад с точки зрения масштабируемости. Чего мне не хватает?

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

Как функциональные языки программирования могут быть поточно-ориентированными, но все же масштабируемыми?

 Vans S15 февр. 2016 г., 23:22
[Не упоминается]: виртуальная машина Эрланга поднимает асинхронность на другой уровень. С помощью магии voodoo (asm) она позволяет синхронизировать операции, такие как socket: read для блокировки без остановки потока os. Это позволяет вам писать синхронный код, когда другие языки вынуждают вас к гнездам асинхронного обратного вызова. Гораздо проще написать масштабирующее приложение с мысленным представлением об однопоточных микросервисах VS, помня об общей картине каждый раз, когда вы что-то добавляете в базу кода.
 Jim Anderson27 мая 2016 г., 20:49
@ Vans S Интересно.

Ответы на вопрос(8)

что позволяет Erlang масштабироваться, связано с параллелизмом.

Операционная система обеспечивает параллелизм двумя механизмами:

процессы операционной системыпотоки операционной системы

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

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

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

Эти процессы Erlang общаются друг с другом, отправляя сообщения (обрабатываются виртуальной машиной Erlang, а не операционной системой). Процессы Эрланга обращаются друг к другу, используя идентификатор процесса (PID), который имеет адрес из трех частей<<N3.N2.N1>>:

процесс N1 наВМ N2 нафизическая машина N3

Два процесса на одной и той же виртуальной машине, на разных виртуальных машинах на одном и том же компьютере или на двух компьютерах взаимодействуют одинаково - поэтому масштабирование не зависит от количества физических машин, на которых вы развертываете свое приложение (в первом приближении).

Erlang является только поточно-ориентированным в тривиальном смысле - у него нет потоков. (Язык SMP / многоядерная виртуальная машина использует один поток операционной системы на ядро).

ожении функции fn (arg1, .. argn) n аргументов могут оцениваться параллельно. Это гарантирует высокий уровень (автоматического) параллелизма.

Эрланг использует модель процесса, в которой процесс может выполняться на одной и той же виртуальной машине или на другом процессоре. Это возможно только потому, что сообщения копируются между процессами, нет общего (изменяемого) состояния. Многопроцессорный паралелизм идет намного дальше, чем многопоточность, поскольку потоки зависят от общей памяти, это может быть только 8 потоков, работающих параллельно на 8-ядерном процессоре, в то время как многопроцессорность может масштабироваться до тысяч параллельных процессов.

потому что она эффективно производит эффект «запуска и ожидания результата», который является синхронной частью, о которой вы читаете. То, что делает это невероятно удивительным, это то, что это означает, что строки не должны выполняться последовательно. Рассмотрим следующий код:

r = methodWithALotOfDiskProcessing();
x = r + 1;
y = methodWithALotOfNetworkProcessing();
w = x * y

Предположим на минуту, что метод methodWithALotOfDiskProcessing () занимает около 2 секунд, а метод methodWithALotOfNetworkProcessing () занимает около 1 секунды. На процедурном языке выполнение этого кода займет около 3 секунд, потому что строки будут выполняться последовательно. Мы тратим время на ожидание завершения одного метода, который может работать одновременно с другим без конкуренции за один ресурс. На функциональном языке строки кода не определяют, когда процессор попытается их выполнить. Функциональный язык может попробовать что-то вроде следующего:

Execute line 1 ... wait.
Execute line 2 ... wait for r value.
Execute line 3 ... wait.
Execute line 4 ... wait for x and y value.
Line 3 returned ... y value set, message line 4.
Line 1 returned ... r value set, message line 2.
Line 2 returned ... x value set, message line 4.
Line 4 returned ... done.

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

РЕДАКТИРОВАТЬ:

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

 Torbjørn Kristoffersen23 июл. 2010 г., 15:51
Отличное объяснение!
 Jim Anderson23 янв. 2009 г., 22:26
Здорово! Я совершенно не понял, как обрабатываются сообщения. Спасибо, ваш пост помогает.
 hcs4229 нояб. 2015 г., 18:35
«Функциональный язык может попробовать что-то вроде следующего» - я не уверен насчет других функциональных языков, но в Erlang пример будет работать точно так же, как и в случае процедурных языков. ВыМожно выполнять эти две задачи параллельно, порождая процессы, позволяя им выполнять две задачи асинхронно и получая их результаты в конце, но это не похоже на то, что «в то время как код является синхронным, он, как правило, имеет другое значение, чем в процедурных языках». Смотрите также ответ Криса.
Решение Вопроса

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

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

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

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

Сравните это с традиционной системой: мы должны разместить мьютексы и семафоры вокруг «защищенных» переменных и выполнения кода. У нас жесткая привязка при вызове функции через стек (ожидание возврата). Все это создает узкие места, которые являются меньшей проблемой в системе с общим доступом, такой как Erlang.

РЕДАКТИРОВАТЬ: я должен также указать, что Эрланг является асинхронным. Вы отправляете свое сообщение и, возможно, когда-нибудь придет другое сообщение. Или нет.

Пункт Спенсера о неисполнении заказа также важен и хорошо ответил.

 Jon Harrop09 нояб. 2011 г., 01:28
@Godeke: «Erlang (как и большинство функциональных языков) сохраняет один экземпляр любых данных, когда это возможно». AFAIK, Erlang на самом деле глубоко копирует все, что прошло между его легкими процессами из-за отсутствия одновременного GC.
 hcs4229 нояб. 2015 г., 18:30
@JonHarrop почти прав: когда процесс отправляет сообщение другому процессу, сообщение копируется; за исключением больших двоичных файлов, которые передаются по ссылке. Смотрите, напримерjlouisramblings.blogspot.hu/2013/10/embrace-copying.html почему это хорошо.
 Godeke23 янв. 2009 г., 22:13
Вы получаете много параллелизмапотенциал в общей системе ничего. Плохая реализация (например, передача большого количества сообщений) может привести к торпедированию этого, но Эрланг, кажется, делает это правильно и сохраняет все легким.
 Aaron Maenpaa23 янв. 2009 г., 22:24
s / оба / беспокоиться / г тьфу ...
 Jim Anderson23 янв. 2009 г., 22:11
Я понимаю это, но не вижу, насколько эффективна модель сообщений. Я бы догадался об обратном. Это действительно откровение для меня. Неудивительно, что функциональные языки программирования привлекают столько внимания.

синхронный с участиемпоследовательный.

Тело функции в erlang обрабатывается последовательно. Так что то, что Спенсер сказал об этом "автоматическом эффекте", не относится к эрлангу. Вы можете смоделировать это поведение с помощью Erlang.

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

Таким образом, мы порождаем процессы, которые выполняют «тяжелые» вычисления (используя дополнительные ядра, если таковые имеются), а затем мы собираем результаты.

-module(countwords).
-export([count_words_in_lines/1]).

count_words_in_lines(Lines) ->
    % For each line in lines run spawn_summarizer with the process id (pid)
    % and a line to work on as arguments.
    % This is a list comprehension and spawn_summarizer will return the pid
    % of the process that was created. So the variable Pids will hold a list
    % of process ids.
    Pids = [spawn_summarizer(self(), Line) || Line <- Lines], 
    % For each pid receive the answer. This will happen in the same order in
    % which the processes were created, because we saved [pid1, pid2, ...] in
    % the variable Pids and now we consume this list.
    Results = [receive_result(Pid) || Pid <- Pids],
    % Sum up the results.
    WordCount = lists:sum(Results),
    io:format("We've got ~p words, Sir!~n", [WordCount]).

spawn_summarizer(S, Line) ->
    % Create a anonymous function and save it in the variable F.
    F = fun() ->
        % Split line into words.
        ListOfWords = string:tokens(Line, " "),
        Length = length(ListOfWords),
        io:format("process ~p calculated ~p words~n", [self(), Length]),
        % Send a tuple containing our pid and Length to S.
        S ! {self(), Length}
    end,
    % There is no return in erlang, instead the last value in a function is
    % returned implicitly.
    % Spawn the anonymous function and return the pid of the new process.
    spawn(F).

% The Variable Pid gets bound in the function head.
% In erlang, you can only assign to a variable once.
receive_result(Pid) ->
    receive
        % Pattern-matching: the block behind "->" will execute only if we receive
        % a tuple that matches the one below. The variable Pid is already bound,
        % so we are waiting here for the answer of a specific process.
        % N is unbound so we accept any value.
        {Pid, N} ->
            io:format("Received \"~p\" from process ~p~n", [N, Pid]),
            N
    end.

И вот как это выглядит, когда мы запускаем это в оболочке:

Eshell V5.6.5  (abort with ^G)
1> Lines = ["This is a string of text", "and this is another", "and yet another", "it's getting boring now"].
["This is a string of text","and this is another",
 "and yet another","it's getting boring now"]
2> c(countwords).
{ok,countwords}
3> countwords:count_words_in_lines(Lines).
process <0.39.0> calculated 6 words
process <0.40.0> calculated 4 words
process <0.41.0> calculated 3 words
process <0.42.0> calculated 4 words
Received "6" from process <0.39.0>
Received "4" from process <0.40.0>
Received "3" from process <0.41.0>
Received "4" from process <0.42.0>
We've got 17 words, Sir!
ok
4> 

как работает Erlang. Среда выполнения Erlang минимизирует переключение контекста на процессоре, но если доступно несколько процессоров, все они используются для обработки сообщений. У вас нет «потоков» в том смысле, в каком вы это делаете на других языках, но вы можете обрабатывать много сообщений одновременно.

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

Похоже, вы перепутали синхронность и последовательность, как упоминалось Крисом.

Ваш ответ на вопрос