Подумал тут я (в очередной раз) про распараллеливание функциональных программ - почитал как это сделано в эрланге, поплевался... вобщем скорее всего будем считать, что удалённо (или просто в другом треде) можно вызвать любую публичную функцию любого модуля (который предварительно отпочкован через spawn) - при этом в отличие от локального вызова это будет выглядеть как асинхронная посылка сообщения без ожидания ответа (ответ придёт обратно путём удалённого же вызова некоторой функции, указанной при посылке):
dec spawn : string -> num; ! встроенная функция, которая запускает Hope-модуль (программу в виде файла .hop) как параллельный процесс (в будущем даже с возможностью запуска на другой машине в сети) и возвращает числовой идентификатор запущенного процесса
dec send : num # (alpha -> beta) # alpha # (beta -> gamma) -> bool; ! встроенная функция, которая осуществляет удалённый вызов некоторой функции (второй аргумент) предварительно запущенного в паралель модуля с известным числовым идентификатором (первый аргумент) с некоторыми аргументами (третий аргумент), кроме того должен быть указан "callback" (четвёртый аргумент) - локальная функция, которая будет вызвана, когда удалённая функция вернёт управление (либо сделать callback необязательным, но тогда придётся сделать две функции send без коллбека и sendx с коллбеком)
Hopeless - перспективы параллельного программирования
Moderator: Shaos
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
Hopeless - перспективы параллельного программирования
Last edited by Shaos on 03 Apr 2010 17:19, edited 2 times in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
пример программы: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]);
Last edited by Shaos on 03 Apr 2010 17:19, edited 1 time in total.
Я тут за главного - если что шлите мыло на me собака shaos точка net
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
с другой стороны предложенный подход неудобен - например как сводить вместе вычисленной на разных узлах? как ожидать когда все вычисления закончатся? вобщем надо делать иначе - удалённый вызов будет возвращать результат в место вызова, но при этом одновременно будут выполняться вызовы одного уровня (скажем внутри тюпла которым является например список аргументов любой функции):
в вышеприведённом примере отпочковываются два параллельных процесса list.hop в которых одновременно вызываются функции reverse - конкатенация списков (оператор <>) осуществляется только если оба результата получены
P.S. а может spawn и не нужен? все равно у нас нету никакого контекста, создавать тред и держать его вечно нет смысла - достаточно создавать тред лишь непосредственно в момент удалённого вызова - можно call переименовать в spawn в этом случае:
на тот конец передаётся имя файла, который уже должен там быть физически - имя нужно чтобы без проблем загрузить его (если он ещё не загружен) и запустить нужную функцию
P.P.S. по идее на этой стороне мы можем и внутри определить откуда взялась эта функция - надо сырцы хопа поизучать повнимательнее, но вроде можно - т.е. передачей имени файла озаботится ядро хопа и программисту об этом думать ненадо:
в этом примере одна половинка списка получается локально, а вторая путём отпочковывания параллельного процесса, причём конкатенация <> вызывается лишь в тот момент, когда оба результата готовы и доступны локально
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]
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
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
ещё один из вариантов распараллеливания - отпочковывание процесса, который становится полностью независимым - например в случае сервера - по приходу нового запроса необходимо отпочковать тред по его обработке, в то время как основной тред продолжает ждать остальные запросыShaos wrote:с другой стороны предложенный подход неудобен - например как сводить вместе вычисленной на разных узлах? как ожидать когда все вычисления закончатся? вобщем надо делать иначе - удалённый вызов будет возвращать результат в место вызова, но при этом одновременно будут выполняться вызовы одного уровня (скажем внутри тюпла которым является например список аргументов любой функции):в вышеприведённом примере отпочковываются два параллельных процесса list.hop в которых одновременно вызываются функции reverse - конкатенация списков (оператор <>) осуществляется только если оба результата получены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]
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
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
Есть идея объединить оба варианта - сделать возможным вызов spawn как синхронно, так и асинхронно (причем с вызовом коллбека либо без оного, если результат работы отпочкованного процесса не интересен родительскому процессу - как в случае с listen - т.е. уже три варианта):
в последнем случае указываем не только функцию, которую надо вызвать, но и имя hop-модуля
P.S. в будущем в строке - идентификаторе модуля можно будет не только модуль указывать, но и адрес узла, на котором это должно быть выполнено (при статической и заранее известной структуре сети)
P.P.S. кстати адрес может быть не конкретный, а типа индекса из списка адресов, передаваемых в hopeless при запуске: 0 - текущая система (localhost), 1 - первый адрес из списка, 2 - второй и т.д., например "1:test.hop"
P.P.P.S. вариант сервера тогда будет выглядеть примерно так:
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; ! асинхронный запуск параллельного процесса с коллбеком
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
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley
подумалось тут, что синхронный вызов spawn не так уж и бессмысленен - например можно запустить удалённо фунцию map:
spawn("field",map,(lambda x => x*x,[1,2,3,4,5]));
при этом вызывающий модуль не будет просто висеть и ждать когда же удалённый конец возведет в квадрат весь список - на самом деле вызывающий модуль будет по одному передавать на тот конец элементы списка и в ответ, опять же по одному, получать результаты (потому как у нас ленивые вычисления) и вокруг этого уже можно строить какую-то разумную параллельную логику
spawn("field",map,(lambda x => x*x,[1,2,3,4,5]));
при этом вызывающий модуль не будет просто висеть и ждать когда же удалённый конец возведет в квадрат весь список - на самом деле вызывающий модуль будет по одному передавать на тот конец элементы списка и в ответ, опять же по одному, получать результаты (потому как у нас ленивые вычисления) и вокруг этого уже можно строить какую-то разумную параллельную логику
Я тут за главного - если что шлите мыло на me собака shaos точка net
-
- Admin
- Posts: 24080
- Joined: 08 Jan 2003 23:22
- Location: Silicon Valley