Hopeless - перспективы параллельного программирования

Использование и разработка софта (преимущественно на ПЦ)

Moderator: Shaos

User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Hopeless - перспективы параллельного программирования

Post by Shaos »

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

dec spawn : string -> num; ! встроенная функция, которая запускает Hope-модуль (программу в виде файла .hop) как параллельный процесс (в будущем даже с возможностью запуска на другой машине в сети) и возвращает числовой идентификатор запущенного процесса

dec send : num # (alpha -> beta) # alpha # (beta -> gamma) -> bool; ! встроенная функция, которая осуществляет удалённый вызов некоторой функции (второй аргумент) предварительно запущенного в паралель модуля с известным числовым идентификатором (первый аргумент) с некоторыми аргументами (третий аргумент), кроме того должен быть указан "callback" (четвёртый аргумент) - локальная функция, которая будет вызвана, когда удалённая функция вернёт управление (либо сделать callback необязательным, но тогда придётся сделать две функции send без коллбека и sendx с коллбеком)
Last edited by Shaos on 03 Apr 2010 17:19, edited 2 times in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Shaos wrote:Подумал тут я (в очередной раз) про распараллеливание функциональных программ - почитал как это сделано в эрланге, поплевался... вобщем скорее всего будем считать, что удалённо (или просто в другом треде) можно вызвать любую публичную функцию любого модуля (который предварительно отпочкован через spawn) - при этом в отличие от локального вызова это будет выглядеть как асинхронная посылка сообщения без ожидания ответа (ответ придёт обратно путём удалённого же вызова некоторой функции, указанной при посылке):

dec spawn : string -> num; ! встроенная функция, которая запускает Hope-модуль (программу в виде файла .hop) как параллельный процесс (в будущем даже с возможностью запуска на другой машине в сети) и возвращает числовой идентификатор запущенного процесса

dec send : num # (alpha -> beta) # alpha # (beta -> gamma) -> bool; ! встроенная функция, которая осуществляет удалённый вызов некоторой функции (второй аргумент) предварительно запущенного в паралель модуля с известным числовым идентификатором (первый аргумент) с некоторыми аргументами (третий аргумент), кроме того должен быть указан "callback" (четвёртый аргумент) - локальная функция, которая будет вызвана, когда удалённая функция вернёт управление (либо сделать callback необязательным, но тогда придётся сделать две функции send без коллбека и sendx с коллбеком)
пример программы:

Code: Select all

uses list;

dec get_reversed_list : list alpha -> num;
--- get_reversed_list(l) <= write(l); ! функцию write пока нельзя так юзать

dec remote_reverse : num # list alpha -> bool;
--- remote_reverse(i,l) <= sendx(i,reverse,l,get_reversed_list);

remote_reverse(spawn("list.hop"),[1,2,3,4,5]);
при вызове она должна напечатать [5,4,3,2,1]
Last edited by Shaos on 03 Apr 2010 17:19, edited 1 time in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

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

Code: Select all

dec spawn : string -> num;
dec call : num # (alpha->beta) # alpha -> beta;
call(spawn("list.hop"),reverse,[6,7,8,9,10]) <> call(spawn("list.hop"),reverse,[1,2,3,4,5])
> [10,9,8,7,6,5,4,3,2,1]
в вышеприведённом примере отпочковываются два параллельных процесса list.hop в которых одновременно вызываются функции reverse - конкатенация списков (оператор <>) осуществляется только если оба результата получены

P.S. а может spawn и не нужен? все равно у нас нету никакого контекста, создавать тред и держать его вечно нет смысла - достаточно создавать тред лишь непосредственно в момент удалённого вызова - можно call переименовать в spawn в этом случае:

Code: Select all

dec spawn : string # (alpha->beta) # alpha -> beta;
spawn("list.hop",reverse,[6,7,8,9,10]) <> spawn("list.hop",reverse,[1,2,3,4,5])
> [10,9,8,7,6,5,4,3,2,1]
на тот конец передаётся имя файла, который уже должен там быть физически - имя нужно чтобы без проблем загрузить его (если он ещё не загружен) и запустить нужную функцию

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

Code: Select all

dec spawn : (alpha->beta) # alpha -> beta;
reverse([6,7,8,9,10]) <> spawn(reverse,[1,2,3,4,5])
> [10,9,8,7,6,5,4,3,2,1]
в этом примере одна половинка списка получается локально, а вторая путём отпочковывания параллельного процесса, причём конкатенация <> вызывается лишь в тот момент, когда оба результата готовы и доступны локально
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Shaos wrote:с другой стороны предложенный подход неудобен - например как сводить вместе вычисленной на разных узлах? как ожидать когда все вычисления закончатся? вобщем надо делать иначе - удалённый вызов будет возвращать результат в место вызова, но при этом одновременно будут выполняться вызовы одного уровня (скажем внутри тюпла которым является например список аргументов любой функции):

Code: Select all

dec spawn : string -> num;
dec call : num # (alpha->beta) # alpha -> beta;
call(spawn("list.hop"),reverse,[6,7,8,9,10]) <> call(spawn("list.hop"),reverse,[1,2,3,4,5])
> [10,9,8,7,6,5,4,3,2,1]
в вышеприведённом примере отпочковываются два параллельных процесса list.hop в которых одновременно вызываются функции reverse - конкатенация списков (оператор <>) осуществляется только если оба результата получены

P.S. а может spawn и не нужен? все равно у нас нету никакого контекста, создавать тред и держать его вечно нет смысла - достаточно создавать тред лишь непосредственно в момент удалённого вызова - можно call переименовать в spawn в этом случае:

Code: Select all

dec spawn : string # (alpha->beta) # alpha -> beta;
spawn("list.hop",reverse,[6,7,8,9,10]) <> spawn("list.hop",reverse,[1,2,3,4,5])
> [10,9,8,7,6,5,4,3,2,1]
на тот конец передаётся имя файла, который уже должен там быть физически - имя нужно чтобы без проблем загрузить его (если он ещё не загружен) и запустить нужную функцию
ещё один из вариантов распараллеливания - отпочковывание процесса, который становится полностью независимым - например в случае сервера - по приходу нового запроса необходимо отпочковать тред по его обработке, в то время как основной тред продолжает ждать остальные запросы

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

tcp_listen(8000,function);

запускаем TCP-слушателя на порту 8000, который по приходу запроса вызовет функцию function перенаправив поток из сокета в список - аргумент функции и возврат функции отправит обратно в сокет, после чего тред закроет

также можно подумать насчёт межпроцессного взаимодействия посредством каналов или ещё чего-нибудь...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Есть идея объединить оба варианта - сделать возможным вызов spawn как синхронно, так и асинхронно (причем с вызовом коллбека либо без оного, если результат работы отпочкованного процесса не интересен родительскому процессу - как в случае с listen - т.е. уже три варианта):

Code: Select all

dec spawn : string # (alpha->beta) # alpha -> beta; ! синхронный запуск параллельного процесса
dec spawna : string # (alpha->beta) # alpha -> truval; ! асинхронный запуск параллельного процесса без возврата результата
dec spawnac : string # (alpha->beta) # alpha # string # (beta->gamma) -> truval; ! асинхронный запуск параллельного процесса с коллбеком
в последнем случае указываем не только функцию, которую надо вызвать, но и имя hop-модуля
P.S. в будущем в строке - идентификаторе модуля можно будет не только модуль указывать, но и адрес узла, на котором это должно быть выполнено (при статической и заранее известной структуре сети)
P.P.S. кстати адрес может быть не конкретный, а типа индекса из списка адресов, передаваемых в hopeless при запуске: 0 - текущая система (localhost), 1 - первый адрес из списка, 2 - второй и т.д., например "1:test.hop"
P.P.P.S. вариант сервера тогда будет выглядеть примерно так:

Code: Select all

type socket == num;
type byte == num; ! ???
type bytes == list byte;
type port == num;
type ip4 == byte # byte # byte # byte;
dec tcp_socket : port -> socket;
dec udp_socket : port -> socket;
dec listen : socket -> bool;
dec ip4_accept : socket -> socket # ip4;
dec read : socket -> list(bytes);
dec write : socket # bytes -> num;
dec reads : socket -> list(string);
dec writes : socket # string -> num;
dec recvfrom : socket -> bytes # ip4; 
dec sendto : socket # bytes # ip4 -> num;
dec recvsfrom : socket -> string # ip4; 
dec sendsto : socket # string # ip4 -> num;
.....
dec server : port -> bool;
dec server_loop : socket -> bool;
dec process : socket # ip4 -> bool;
dec process_line : socket # string -> string;
--- server(port) <= let s==tcp_socket(port) in s!=0 and listen(s) and server_loop(s);
--- server_loop(s) <= spawna("this.hop",process,ip4_accept(s)) and server_loop(socket);
--- process(s,addr) <= writes(s,process_line(s,reads(s)));
--- process_line(s,"") <= ""; ! empty reads means end of connection
--- process_line(s,line) <= line <> process_line(s,reads(s)); ! send back the same thing
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

подумалось тут, что синхронный вызов spawn не так уж и бессмысленен - например можно запустить удалённо фунцию map:

spawn("field",map,(lambda x => x*x,[1,2,3,4,5]));

при этом вызывающий модуль не будет просто висеть и ждать когда же удалённый конец возведет в квадрат весь список - на самом деле вызывающий модуль будет по одному передавать на тот конец элементы списка и в ответ, опять же по одному, получать результаты (потому как у нас ленивые вычисления) и вокруг этого уже можно строить какую-то разумную параллельную логику
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Post by Shaos »

Суть моего подхода в том, что ленивые списки используются в качестве каналов передачи данных между процессами (т.е. никаких других абстракций для каналов создавать больше ненадо)
Я тут за главного - если что шлите мыло на me собака shaos точка net