Hello. as the subject indicates, I would like to write some code where a secondary thread does something and sometimes posts an event to the main thread, passing calculated data.
In other terms, the posted event translates into a callback on a listener and the callback has to be called inside the main thread.
Is there an easy way to do this?
Another question:
if I replace the main event loop with my_event_loop, as stated in the documentation:
tg->server_set_event_loop(my_event_loop);
does the Tango server still receive tango device server events or do I have to worry about that in my_event_loop?
About your first question, I never implemented something like that with callback to be called from the main thread.
Usually, we have cases where we have an acquisition thread which will will receive updates from the hardware and which will update some shared variables (protected with mutex) between the acquisition thread and the main thread.
Then, there are polled attributes or commands which will use these variables.
Maybe someone else already did what you’d like to do?
It should work, the Tango server should still receive Tango events. You shouldn’t have to worry about that in your event loop.
Hello and thanks for your reply Reynald.
One more question naturally follows your observations: is it safe to share (reading and writing) objects inside my_event_loop and the Tango main event loop?
CORBA creates one thread per client and there is also the Tango polling thread running in parallel of your main thread.
So if these threads (or other threads) could be accessing the same objects as your custom event loop, you should protect these objects in your custom event loop using Tango monitors or mutexes.
Do I have to read/write protect “var” from within read_double_scalar and MyEventLoop::some_func()
?
As far as I can understand from what you’ve written above, I suspect so.
In that case, I would rather understand how to post events to the thread where
void MyTangoDev::read_double_scalar
is executed.
I explain why. I am trying to write a library where you can do activities in background and get a notification
when they are finished. The notification arrives in the main thread together with the data. The advantage of this
is not having to worry about locks mutexes and threads. If this cannot be applied due to concurrency between MyEventLoop
and main server loop, than it’s not useful at all.
So, how to post events in the (main) MyTangoDev thread, so that I can safely access data posted from the background threads?
I don’t see how you could avoid dealing with mutexes in a multithreaded environment.
If you want to access to the variable var you were describing from read_double_scalar and from the main thread (in your custom event loop or from another thread in general), you need to protect this access. Well, in short, if you need to access this variable from different threads, you need protection.
read_double_scalar might be invoked by your polling thread or by a thread created to handle a client request.
read_double_scalar is usually not executed from the main thread. CORBA will create a thread to handle a client request and will execute read_double_scalar in this thread.
You might get the illusion it is executed by the main thread because of the Tango serialization mechanism which will protect you automatically by default (using mutexes hidden in the Tango implementation) against concurrent executions of read_double_scalar method but this method is actually executed in a thread different than the main thread.
[quote=“Reynald”][quote=“giacomo”]
Do I have to read/write protect “var” from within read_double_scalar and MyEventLoop::some_func()?
As far as I can understand from what you’ve written above, I suspect so.
[/quote]
You understood well.
I don’t see how you could avoid dealing with mutexes in a multithreaded environment.
Of course you need them!
But when two threads exchange data, they can use message queues, which are mutex protected. Then one thread notifies the other that there’s data in the queue. This is what I usually do when dealing with GUIs and background activities. For this I’m asking you how to access the message queue of the device server event loop so that the attributes (variables) declared in MyTangoDev class can be accessed without protection.
Let’s have
MyDeviceServer class with a DevDouble var;
MyBackgroundActivity, that generates a DevDouble at random every 3 seconds.
/* HERE I NEED A WAY TO PUT THIS DOUBLE IN A QUEUE AND NOTIFY MyDeviceServer event loop */
my_device_server->postEvent(rand);
}
/* 2 */
a callback MyDeviceServer::resultEvent(DevDouble data) to be invoked in the MyDeviceServer thread (same as where MyDeviceServer::read_double_scalar() is called) so that it is safe to write:
var = data;
If you want to access to the variable var you were describing from read_double_scalar and from the main thread (in your custom event loop or from another thread in general), you need to protect this access. Well, in short, if you need to access this variable from different threads, you need protection.
read_double_scalar might be invoked by your polling thread or by a thread created to handle a client request.
read_double_scalar is usually not executed from the main thread. CORBA will create a thread to handle a client request and will execute read_double_scalar in this thread.
You might get the illusion it is executed by the main thread because of the Tango serialization mechanism which will protect you automatically by default (using mutexes hidden in the Tango implementation) against concurrent executions of read_double_scalar method but this method is actually executed in a thread different than the main thread.
What do you mean with MyDeviceServer event loop? Do you mean your custom event loop? Or the message queue used by Tango to deal with Tango events?
I don’t know how you could ensure your callback is called from the same thread as MyDeviceServer::read_double_scalar() unless you are calling your callback directly from MyDeviceServer::read_double_scalar().
As I wrote before, CORBA will create a thread per client to handle requests (read or write an attribute, execute a command).
So if you have 2 clients for instance, reading double_scalar attribute, MyDeviceServer::read_double_scalar() will be invoked from at least 2 different threads. So your assumption that it is safe to write a variable from the same thread as where MyDeviceServer::read_double_scalar() is invoked is wrong because this method can be invoked from several threads.
this being a pointer to your DeviceImpl device object. The AutoTangoMonitor takes into account the serialization model used by your device server as described in section 7.4.1.1 (serialization model within a device server) of the Tango Book.
The Tango main thread does mainly the following: it listens for new CORBA requests, create one thread per client and dispatch requests to the different threads. It does not invoke MyDeviceServer::read_double_scalar().
Sorry if I misunderstood your use case again but maybe this will at least put a bit more light on the AutoTangoMonitor feature.
Hello Reynald, thanks for Your prompt replies.
Maybe yes, still we don’t get each other.
Take TangoTest as an example. Suppose we have 2 clients (jive) from 2 distinct machines. Let’s say we read double_scalar from the two machines.
I give (or have given, up to now) for granted that inside TangoTest DeviceImpl (and yes, I was referring to code inside DeviceImpl) I don’t have to worry if the DevDouble class member attr_double_scalar_read is called from 2 (or N) separate clients (and so modified from 2 (or N) different threads. Actually, inside
All I want is to know how to invoke a method in this exactly very same context from a background thread when data is ready to be passed to DeviceImpl::my_callback(MyData newData), just as I imagine it is done when from the network a packet coming from Jive (with the double_scalar read request) is received and the void TangoTest::read_double_scalar(Tango::Attribute &attr) is invoked in the client thread.
That’s it!
If it’s too complicated, never mind, it doesn’t make sense to write a library that makes things more complicated rather than simpler!
[quote=“giacomo”]Hello Reynald, thanks for Your prompt replies.
Maybe yes, still we don’t get each other.
Take TangoTest as an example. Suppose we have 2 clients (jive) from 2 distinct machines. Let’s say we read double_scalar from the two machines.
I give (or have given, up to now) for granted that inside TangoTest DeviceImpl (and yes, I was referring to code inside DeviceImpl) I don’t have to worry if the DevDouble class member attr_double_scalar_read is called from 2 (or N) separate clients (and so modified from 2 (or N) different threads. Actually, inside
I don’t see any precaution taken against this.
[/quote]
Indeed, because the Tango library is putting all the protections (using Tango monitor as I described) for you before these methods are called. But you should not call these DeviceImpl methods directly from another custom thread without asking for the Tango monitor first.
If you take the TangoMonitor before executing the callback which will modify your class member attr_double_scalar_read for instance, you are safe.
By taking the monitor, you ensure that NO OTHER REQUEST which concerns your device, devices of the same class in the same process or all devices in your process (depending on your Tango serialization model) will be executed in parallel as your callback.
Simply do that in the part of the code which is invoking your callback:
// Get the data from the message queue
MyData data = getDataInAThreadSafeWayFromMessageQueue();
// Call the callback
{
Tango::AutoTangoMonitor synch(dev); // dev is the DeviceImpl pointer to your device
MyCallback(data);
}
Of course, you need to find a way to pass the pointer to your DeviceImpl to your thread (could be part of the data transferred from your message queue).
If I still didn’t understand what you want to do, we could have a chat next week. It might be easier to understand each other…
would a simple solution to Giacomo’s problem not be to create a DeviceProxy and call read_attribute() from the background thread. This would take care of the conflicting accesses and avoid the need to create mutexes. This would be simulate the case of an external client. Just an idea.
Now it’s clear to me Reynald, that’s the answer I was looking for.
Thanks Andy, this time I was looking for server side general tasks, not necessary involving Tango clients.
There are three possibilities to achieve my purpose now:
invoke a callback from my secondary thread(s) using tango monitor to get the lock.
In this case the callback would be called from N different threads;
write an event loop, post the events from the N background threads to my custom event loop. Then acquire the monitor and invoke the callback from the custom event loop. This means the callbacks would be called by the thread where the custom event loop lives (only one thread). The custom event loop should be installed with tg util set_server_loop().
“awaken” the ORB event loop so that messages are popped from a queue and process in the same thread as, let’s say, where MuDeviceImpl::read_double_scalar’s.
Is solution 3 easily achievable?
What do you think is best between 1 and 2, if 3 not possible?
Hello Reynald, I realise that point 3 does not make sense. Maybe yesterday at 6pm I was tired…
I will go for implementation in point 2.
Thanks for Your support.
Have a good day.
Giacomo.
Please note that if you use the possibility 2, the event loop function passed to server_set_event_loop() will be executed in a loop once every 20 ms at the maximum…
Looking at the Util::server_perform_work() method which is called at some point from the main thread might help you to better understand how is handled the custom event loop feature: cppTango/cppapi/server/utils.cpp at tango-9-lts · tango-controls/cppTango · GitHub
If you need something more reactive than 20ms, you should change the Tango source code or use another solution.
Cheers,
Reynald