How to run UObject C++ methods in parallel
Handling parallelism in Urbi is easy as long as you stay in a pure Urbi world, containing only Urbi code. Urbi is a parallel language and that’s exactly what it does. But as soon as you start to plug C++ code via UObjects, things start to be more complicated because the current 1.x version does not yet fully extend the parallel semantics of the language to the C++ side.
We will see here a practical example of a typical issue and several ways to go around the problem with the current versions of Urbi (1.x).
Let’s assume that you have an UObject ‘obj’ which is plugged in Urbi, with two methods ‘f’ and ‘g’ that are doing some heavy calculation which takes some time to execute. Let’s say that you want to run in parallel the execution of ‘f’ and ‘g’, using the usual Urbi & operator:
obj.f() & obj.g(),
If “obj” is plugged in Urbi (linked to the engine), the current version of the kernel will not run the two calls in parallel (a multithread version is in preparation, but it is not available yet), but it will run “f” and then “g” one after the other. Also, if “f” or “g” are not returning, caught in some infinite loop for example, then the whole Urbi kernel will be frozen.
There are two workaround possible here. The first is to use remote UObjects instead of plugged UObjects. In that case, “obj” is actually running as a separate process, possibly on localhost, and is not linked in the kernel. Each call to one of its methods is relayed on the network in an asynchronous way, which means that “f” and “g” will be executed in parallel (both calls will be launched) and the kernel will not be frozen if “f” or “g” does not return. In 90% of the case, that will solve your issue, as most UObjects should in principle run as remote UObjects anyway, unless they are critical system UObjects like drivers. Drivers and critical processes are usually using fast return methods and so the problem should not arise at all in that case.
One detail however: the executions of “f” and “g” in the remote UObject will of course not occur in parallel, since we are talking to the same UObject and the same process. “f” will be executed, then “g” will be executed, but the execution flow on the Urbi side will patiently wait for both to return. Now if you are talking to two different UObjects “obj1″ and “obj2″, “f” and “g” will be run in parallel in the process attached to “obj1″ and in the process attached to “obj2″. So if you critically need to run remote functions in parallel, you may try to split them into two remote UObjects.
The second workaround is a straightforward one and it works both in plugged and remote mode: you can manage yourself your own thread inside “f” and “g” to detach the execution of the functions. The simplest way to do this on the Urbi side is to call “f” and “g” at regular intervals to check if the thread has finished its job and then return the final value, all this wrapped in some Urbi code. So we rename “f” into “f_thread” and “g” into “g_thread”, which returns 0 when their thread is still running, 1 otherwise. They store the result of their calculation in a tmp variable sent as a parameter. The result for “f” is:
// f wrapper
function obj.f()
{
var obj.f_result;
var finished = 0;
while (!finished)
finished = obj.f_thread("obj.f_result");
return obj.f_result;
};
This can be further refined by creating a unique f_result variable per function call, and having some obj.f_createthread and obj.f_callthread methods to be able to run several instances of “f” in parallel, like in “obj.f() & obj.f()”.
In a next post, we will see what is in preparation in Urbi 2.x to improve the way C++ parallel methods are handled, in particular a coroutine mechanism integrated in UObject to allow you to yield back to the Urbi kernel in the middle of your C++ code, and be recalled automatically at this precise place on the next kernel cycle, making non-threaded parallelism possible on the C++ side.
Posted: January 11th, 2008 under Workarounds.
Comments: none