How to use persistent variables inside parfor method of a custom object class?
11 views (last 30 days)
Show older comments
Paul Shoemaker
on 3 Sep 2019
Answered: Paul Shoemaker
on 15 Sep 2019
There is a similar entry on using persistent variables with spmd/parfor (https://www.mathworks.com/matlabcentral/answers/233648-spmd-and-persistent-variables), but the solution doesn't appear to work when applied to object classes.
Here's a very simplified example where I'd like the persistent variable inside my object class to persist across parfor iterations called by another method in the class:
classdef example
methods(Static)
function output = parforFun(n)
output = cell(n,1);
parfor x = 1:n
output{x} = example.persistentFun;
% Do something with output (not shown here)
end
output = cell2mat(output); % Ideally, every index of "output" is identical
end
function output = persistentFun
% Return the persistent variable myConstant
persistent myConstant
if isempty(myConstant)
myConstant = rand; % Define the term. Real code involves calling web service.
end
output = myConstant;
end
end
end
Why do this, you ask? In my real application, "example" is a web service class I created. I'm getting an authentication token to a web service in persistentFun so I don't want each worker to ask for its own authentication token. ***Importantly, I can't move the persistentFun call before the parfor and then distribute the token, because the token expires periodically and may expire while my parfor is running. Therefore, I want to call PersistentFun inside the parfor and not before, then let persistentFun determine when to request a new token and distribute to all workers. Also, I'd like to avoid the clunky hack of writing the token to a file and then fetching it, if possible.
Back to the example....
Now call output = example.parforFun(5) and see if the value of output is different for every index, which would indicate each parfor is using a different value for persistent myConstant.
output = example.parforFun(5)
output =
0.350625385914416
1.91633571411975
1.85656023059334
1.17686837315114
4.30311363531808
You can see that the persistent "myConstant" changes between parfor calls when I would like for it to be constant. Of course, if you set a large enough input value (approaching or greater than number of workers) then you start to see repeating values, which indicates that each worker is starting to get called more than once and is retrieving its persistent variable.
By the way, the idea of putting into a separate file (i.e. creating a @example folder, adding to path, and putting everything in there) and using "addAttachedFiles(gcp,{'persistentFun.m'})" doesn't seem to resolve this issue.
I'm not seeing an obvious way to do this without writing the token to a file and then reading, but I'd like to avoid this. Any ideas?
Thanks,
Paul S
2 Comments
Walter Roberson
on 3 Sep 2019
I suggest using parfeval() to dispatch workers and parfEvalOnAll to update the tokens.
Accepted Answer
Walter Roberson
on 3 Sep 2019
Outline:
Initialize token
set up a timer to refresh the token
loop, parfeval() as many jobs as there are workers. They should get allocated one per worker, but that is not technically guaranteed
set up a foreach() that stores the result received and if there are more iterations to be done, parfeval() another iteration with the current token (which is being refreshed from time to time by the timer)
wait for all the iterations to be done, and shut down
The timer interval has to be such that if a job were started immediately before the timer fired, that the previous token will still be valid for the duration of the job -- so it has to update no more than (hard expiration duration minus maximum job duration.)
2 Comments
Walter Roberson
on 3 Sep 2019
There is a way that a client can update on an active worker: if you create a data queue or pollable queue;
The trick is that you can create a queue before a parallel construct, and it will be copied over to the workers. The first thing you do on the worker is then to create a queue on the worker, and use the first queue to send the second queue back to the client. You now have an per-worker endpoint on the client that you can use to send an updated token to the worker. I think a pollable queue would make the most sense to construct on the worker, as the worker could then periodically check to see if there is anything in the queue and if so use it to update the local token.
More Answers (3)
Matt J
on 3 Sep 2019
Edited: Matt J
on 3 Sep 2019
Here's one way to do it, sort of along the lines that Walter proposed, but with externally scoped variables instead of persistent variables,
function test
n=10;
Token=rand;
updateExecuted=false(1,n);
tic;
for i = 1:n
future(i) = parfeval(@LongTask,1);
if rand>0.7%Simulate token updates at random times
updateToken();
updateExecuted(i)=true;
end
end
toc; %Elapsed time is 0.026488 seconds.
tic;
for i = 1:n
[completedIdx,value] = fetchNext(future);
output(completedIdx) = value;
end
toc; %Elapsed time is 5.022906 seconds.
output,
updateExecuted,
function t=LongTask
t=Token;
pause(5); %simulate some fake time-consuming step
end
function updateToken
Token=rand;
end
end
Executing this on (12 workers), we see that the output of the workers remains constant between the points where token updates occurred.
>> test
output =
0.9303 0.9303 0.9303 0.9303 0.9303 0.9750 0.9750 0.7657 0.7657 0.7657
updateExecuted =
1×10 logical array
0 0 0 0 1 0 1 0 0 0
6 Comments
Matt J
on 4 Sep 2019
Edited: Matt J
on 4 Sep 2019
I guess it's also possible the value is cloned to the worker when parFeval is issued... I really don't know.
But I think in the scenario described by the OP, a decision is made at some point by the worker to accept a token as valid. So, some desynchronization with the host copy of the token must inevitably be tolerated.
See Also
Categories
Find more on Asynchronous Parallel Programming in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!