Parfor loop with classes and listerner

I have 2 classes as follows:
classdef ToggleButton < handle
properties
rx_bits = [0;1;0;1;1];
iteration = 1;
end
events
ToggledState
end
methods
function new_iteration(obj, new_iterations, new_bits)
if new_iterations ~= obj.iteration
obj.iteration = new_iterations;
obj.rx_bits = new_bits;
notify(obj,'ToggledState');
end
end
end
end
classdef myClass < handle
properties
value
end
methods
function obj = myClass(toggle_button_obj)
addlistener(toggle_button_obj,'ToggledState',@(src,evnt)obj.evntCb(src,evnt));
end
function obj = evntCb(obj,src,~)
obj.value(src.iteration) = sum(src.rx_bits);
end
end
end
As it can be seen, the 1st class has an event which notifies the second class. There is a listener in the second class which waits for this notification, so once notified it updates its value property.
I am trying to execute this in a loop saving the values using the script:
rx = ToggleButton;
performance = myClass(rx);
for i = 1:10
rx.new_iteration(i,round(rand(2,1)*10))
end
If I execute the above I get the expected result and the property value is updated each iteration. However, if I change the for loop to parfor it does not work anymore. I do not get any error message but the property value is empty after execution.
Any suggestion on why this happens and how to fix it?

 Respuesta aceptada

Walter Roberson
Walter Roberson el 21 de Ag. de 2023

0 votos

parfor operates in separate processes. The object is being loaded into the separate processes, and any changes to the object in the separate processes are not copied back.

rx will not be shared between the parfor workers.

11 comentarios

Miroslav
Miroslav el 21 de Ag. de 2023
Hi @Walter Roberson, thank you for the reply. Is there a way to share the objects between workers and have the changes copied back?
Walter Roberson
Walter Roberson el 21 de Ag. de 2023
After the parfor loop, you could use parfevalOnAll to request the value of the objects. Only serializable portions of the objects will be copied -- the process will be the same as save() on the worker and load() on the client.
Miroslav
Miroslav el 21 de Ag. de 2023
@Walter Roberson I struggle to find a clear explanation of parfevalOnAll and how exactly to use it in my case. Are you aware of any source which provides examples on how to use it? From what I understand parfevalOnAll should be used instead of parfor. Unfortunately, I cannot find good examples.
Walter Roberson
Walter Roberson el 21 de Ag. de 2023
Editada: Walter Roberson el 21 de Ag. de 2023
F = parfevalOnAll(@(varargin)rx, 1);
wait(F);
rxes = fetchoutputs(F, 'uniformoutput', 0);
The result rxes should be a cell array of the rx objects, one cell entry for each worker (not for each iteration.) I have it request a cell array because I did not want to bother to research whether your class supports array of objects.
Miroslav
Miroslav el 23 de Ag. de 2023
Hi @Walter Roberson, I want to save performance not rx. By just substituting rx in the 1st line with performance I get some strange results.
1) I have a loop of i=1:10 but the output array has only 8 objects. I have noticed that this is independent from i, hence if I do i=1:2 I get the same output.
2) The 8 objects that are saved are still empty as what I was getting before.
3) Similar things happen if I use rx, as in your example above. I get only 8 rx objects as output and they are copy of each other and all have the initialization values, i.e., nothing is updated during the loop
You will get one returned object per worker, not per iteration.
Your for loop does not reset statistics or values or whatever for every iteration: you initialize a pair of objects and you iterate changing the state of the object, with each new iteration updating the objects.
When you parfor then you do the equivalent of save() of the initial state of the object on the client, then load() it once per worker and then the loop for that worker changes the state of that worker's objects independently of the other workers. With 8 workers and 10 iterations, most workers would end up handling only one iteration but two of the workers would probably end up handling two iterations. Then after the parfor is completed you can use parfevalOnAll to copy the states of the objects back -- one per worker.
rx = ToggleButton;
performance = myClass(rx);
I would suggest to you that it would be more robust to instead do
parfevalOnAll(@init_rx, 0)
with
function init_rx
rx = ToggleButton;
performance = myClass(rx);
end
I can tell that what you were expecting was that parfor would have single rx and performance objects that would be shared between the workers, and that the single objects would just be updated faster due to the multiple threads executing. But that is not at all how parfor works: parfor does not share objects between workers. And if it did share objects between workers, then you would have the problem that you did not use locking or barriers so the different workers could potentially interfere with each other.
Look at your existing code:
if new_iterations ~= obj.iteration
obj.iteration = new_iterations;
obj.rx_bits = new_bits;
notify(obj,'ToggledState');
end
What happens if you get two updates of that variety before the asynchronous function evntCb could get served? Callbacks get queued . When does the queue get served? Documentation on that is difficult to find:
  • graphics objects are defined as having the queue examined when there is next a waitfor() or uiwait() or pause() or drawnow() or figure() call or MATLAB returns to the command line. Graphic objects provide explicit control over whether their callbacks are interruptable and if not whether additional graphic callbacks that come in should be queued or discarded
  • timer() objects are documented as being served at the beginning of the next MATLAB source code line that is executed. So if you were currently executing built_in_function1(); your_m_function(); then it would not interrupt built_in_function1 but when it hit the first executable line inside your_m_function it would be served
  • spmd labSend() and labReceive() and labBarrier() have some explicit blocking semantics
I have not found any documentation about when listeners are served; there can be a lot of listeners for various purposes. I have not seen any description about how to do mutexes in MATLAB or how to avoid problems with mutual deadlock.
Walter Roberson
Walter Roberson el 23 de Ag. de 2023
You can construct parallel.pool.DataQueue and parallel.pool.PollableDataQueue to create communications channels between workers and the client.
The basic useage would be to construct a queue on the client, and then when you parfor then the queue will be copied to all of the workers, and the workers can deliberately send data back to the client. The client can use afterEach to execute an asynchronous function when it receives data from the worker -- so it could update data structures that live on the client.
There is an advanced useage. You can start by constructing a queue on the client like for the basic worker. Then use parfevalOnAll() to create a dataqueue on each worker, and have the worker use the original dataqueue to transmit the just-constructed per-worker dataqueue to the client. This will allow the client to retreive one queue per worker. Now the workers can use the original dataqueue to send data to to client, and the client can use the appropriate per-worker dataqueue to send data to an individual worker. Workers cannot send data directly to each other, but the client can act as a switch-yard. For example if a worker finds a new "best" value of a computation, then it could transmit the details to the client, and the client can then send the details to each of the workers so that they would have information about the current "best".
dataqueues are not really designed for high performance; do not count on them to transmit a lot of data with short latencies. However you could use them to load and pre-filter images or data files on workers and transmit the data to the client. (Though if you were doing that, then keep in mind that beyond roughly two processes per drive channel per drive controller, you will probably run into resource contention. Trying to have 8 workers all read from the same non-RAID filesystem is going to tend to be slower than having two workers doing all of the reading.)
Miroslav
Miroslav el 24 de Ag. de 2023
Thanks @Walter Roberson, seems like involving listeners in the process is not straightforward. I will probably re-design the classes.
Sam Marshalik
Sam Marshalik el 24 de Ag. de 2023
Miroslav, before doing that, I would suggest reaching out to MathWorks Technical support.
I think DataQueue could potentially help you here. It is capable of sending information between the workers and the MATLAB client and if set up properly can also accomadate worker to worker communication. This would enable your parfor workers to pass information to one another and may resolve this issue.
Walter Roberson
Walter Roberson el 24 de Ag. de 2023
Dataqueues have historically been documented as not permitting worker to worker communication. There was always the question of what would happen if you constructed queues on the workers, sent them to the client, and the client sent them to the other workers, but the documentation always said no worker to worker was possible.

Iniciar sesión para comentar.

Más respuestas (0)

Categorías

Más información sobre Code Execution en Centro de ayuda y File Exchange.

Productos

Versión

R2023a

Preguntada:

el 21 de Ag. de 2023

Comentada:

el 24 de Ag. de 2023

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by