Cinco, llamada remota dos
Esta sección habla de tres comandos de macro que son convenientes para llamadas remotas.
Comando macro@everywhere
Un caso especial de llamadas remotas es hacer que la declaración / función / expresión se ejecute en todos los procesos. Por ejemplo: abra 1 trabajador y luego declare una variable en cada proceso (proceso principal + 1 trabajador) beta
y calcule una expresión beta+1
. Usando el método descrito anteriormente, se puede escribir como:
julia> for pid = 1:2
r = @fetchfrom pid (beta=0;beta+1)
println(r)
end
1
1
Se utiliza println()
para imprimir los resultados, porque el REPL predeterminado para imprimir los resultados de la última línea, y en este ejemplo, la última línea es end
, y no se muestran resultados. Recordatorio especial: use paréntesis cuando llame a varias expresiones de forma remota; de lo contrario, solo se llamará a la última, lo que dará como resultado beta
indefinido, como este:
julia> for pid = 1:2
r = @fetchfrom pid beta=0;beta+1
println(r)
end
ERROR: UndefVarError: beta not defined
¿Se puede hacer en un solo paso? ¡Tener! Eso es todo @everywhere
. Puede abreviarse como:
julia> @everywhere (beta=0;beta+1)
Es una transmisión de una expresión, que se ejecutará en todos los procesos. Pero no devuelve un objeto Future, y el resultado no es visible, por lo que generalmente se usa para declaraciones de transmisión, y las expresiones subsiguientes aún usan @fetch
llamadas remotas como "comandos mono" para devolver los resultados. Por supuesto, podemos hacer un pequeño truco, @everywhere
difundir algunas expresiones que no necesitan devolver resultados y que son compartidas por todos los procesos , y luego solo usar el "comando único" para obtener los resultados de las expresiones que deben devolver el resultado al final. Será más conciso escribir de esta manera.
Comando macro@eval
@eval
Los comandos se utilizan para realizar "sustitución de valor" en expresiones. La denominada "sustitución de valor" aquí significa evaluar primero la expresión y luego reemplazar la posición de la expresión con el valor. También es posible "sustitución de valores" para una sola variable en la expresión, simplemente agregue un $
símbolo antes de la variable . Dado que este comando tiene una alta prioridad, podemos usarlo para realizar algunas operaciones sao, como usar variables locales en Worker:
julia> beta = 0;
julia> @eval @everywhere $beta+1
Que entra en vigor antes @eval
que @everywhere
, beta
reemplaza con 1
, por lo que @everywhere
no se informará ningún error. Para demostrar que la @eval
prioridad es realmente mayor, puede intentar:
julia> p = 0; @fetch @eval $p+1
1
# 或者
julia> p = 0; @everywhere @eval $p+1
Se puede ver que @eval
la parte delantera y trasera son iguales.
A modo de comparación, echemos un vistazo al informe de errores:
julia> kappa = 0; @everywhere kappa+1
ERROR: On worker 2:
UndefVarError: kappa not defined
Tenga cuidado de no usarlo aquí beta
, porque lo anterior ya se ha beta
difundido, por lo que ya hay beta
declaraciones en cada proceso .
Comando macro@distributed
Permítanme hablar sobre un comando macro específicamente para el bucle for @distributed
. Su uso es
@distributed (聚合函数) for var = range
表达式
end
Si hay varias expresiones, el valor de la última expresión participa en la agregación. La función de agregación es un parámetro opcional, si está vacía, significa que no hay agregación. P.ej:
julia> s = @distributed (+) for i = 1:10
2*i
3*i
end
165
Se puede ver que agrega los resultados de la última fila. Si no escribe una función agregada, el valor devuelto ya no es un valor. En una sola computadora, el @distributed
paralelismo a nivel de corrutina se usará primero, por lo que se devuelve una tarea, de la siguiente manera:
julia> s = @distributed for i = 1:10
2*i
3*i
end
Task (queued) @0x00000000079d99f0
Tenga en cuenta que la tarea está en la cola, porque regresa inmediatamente cuando se crea. Ya sea que escriba funciones agregadas o no, el programador de Julia siempre lo programará automáticamente para que se ejecute en el momento adecuado. Podemos comprobar su ejecución en cualquier momento:
julia> istaskdone(s)
true
En cuanto a cómo especificar el @distributed
paralelismo a nivel de proceso y si el paralelismo a nivel de proceso @distributed
se utilizará primero en un grupo de computadoras , el libro no está escrito con claridad y debe ser probado.
Seis, referencia remota
Todas las llamadas remotas mencionadas anteriormente se basan en la transferencia de datos entre procesos, es decir, los datos de cada proceso están aislados entre sí y la cooperación se realiza mediante referencias remotas. Una referencia remota es un objeto, dividido en objetos Future y objetos RemoteChannel. El primero está referenciado desde un trabajador al proceso principal, y el último se crea y almacena en un trabajador y es visible para todos los procesos.
Regala una castaña para ilustrar:
julia> c = Channel(2)
Channel{Any}(sz_max:2,sz_curr:0)
julia> @fetchfrom 2 put!(c,10)
10
julia> isready(c)
false
julia> @fetchfrom 2 isready(c)
true
Este tipo de referencia remota se implementa utilizando objetos Future. Como se muestra en la figura, cuando c
se crea el proceso principal (rojo) como un parámetro de llamada remota, se crea una copia (verde) en el Trabajador, se ejecuta la expresión para obtener el resultado (azul), y el resultado se extrae y almacena en el Futuro del proceso principal (Entonces este Futuro en realidad c
no tiene nada que ver con lo local ). La operación en el trabajador solo cambia el estado de la copia sin afectar el proceso principal.
Tenga en cuenta que el resultado @fetchfrom
de la expresión put!(c,10)
(azul) se extrae , por lo que el resultado (azul) se eliminará del Worker, pero c
la copia (verde) como parámetro no se eliminará y todavía existe en el Worker, por lo que puede Continúe utilizándolo en el control remoto.
Si queremos c
modificar el Worker desde el proceso principal en cualquier momento durante la operación del par Worker c
, entonces las referencias remotas basadas en Future mencionadas anteriormente no funcionarán. Con este fin, Julia proporciona uno RemoteChannel
. Un ejemplo del método de creación es el siguiente:
julia> f = ()->Channel{Int}(10)
#47 (generic function with 1 method)
julia> r = RemoteChannel(f,2)
RemoteChannel{Channel{Int64}}(2, 1, 45)
El primer paso es declarar una función f
(ver la sintaxis de funciones anónimas), que debe devolver un canal. El segundo paso RemoteChannel(f, 2)
es crear un RemoteChannel en el Worker con PID = 2 y tener f
los mismos atributos que el Channel devuelto. Por supuesto, escribir de esta manera dejará un proceso principal redundante f
, por lo que dos oraciones se escriben en una oración en el libro:
julia> r = RemoteChannel(()->Channel{Int}(10),2)
RemoteChannel{Channel{Int64}}(2, 1, 47)
r
Es el identificador de este RemoteChannel, ubicado en el proceso principal. Podemos r
modificar el RemoteChannel mediante operaciones , como un dron de control remoto. Por ejemplo isready(r)
, put!(r,100)
etc. también incluyen extracción fetch(r)
y take!(r)
. Además, podemos pasarlo como parámetro a cualquier Worker y luego operar allí, por ejemplo:
julia> @fetchfrom 3 put!(r,100)
RemoteChannel{Channel{Int64}}(2, 1, 47)
julia> take!(r)
100
julia> isready(r)
false
Aquí r
ponemos un elemento en el Worker con PID = 3 , y luego lo extraemos en el proceso principal, y finalmente comprobamos que r
está vacío en el proceso principal , y se puede ver que r
está compartido.
Si no se especifica ningún PID al crear un RemoteChannel, se creará en el proceso principal de forma predeterminada. No importa dónde se cree, las operaciones en el identificador harán que los datos pasen entre el "proceso de operación" y el "proceso de creación". Si los datos son grandes, esta transferencia consumirá mucho tiempo. El concepto de "matriz compartida" se presenta a continuación para resolver este problema.