User:Andrecaldas/Multithread

= Ideas = Many things in FreeCAD depend on some heavy computations. Of course, parallel processing could take advantage of multi-core processors. Many heavy to compute methods could be executed in parallel, delivering a faster result.

But not only that! The GUI could be much more responsive if it did not freeze while waiting for such processes to finish their tasks.

FreeCAD::recompute
When we call FreeCAD::recompute, the document objects are organized in a list, according to their dependency. Then each feature is recomputed, one by one in a linear order.

Taking a little more advantage of multithreading, we could have, for example, one thread for each recomputed object. The method FreeCAD::recompute could return almost immediately. Each object would be associated to one std::condition_variable to indicate completion. When object B depends on object A, its corresponding thread would block, waiting to be notified about the corresponding condition_variable. When A completes, it can notify all threads that are waiting.

This does not involve complicate signaling mechanisms. Of course, cyclic dependencies need to be avoided, to prevent deadlocks... but they are already.

The pool of condition variables shall not be members of the corresponding objects. They should be "local" to the pool of threads related to this specific call to FreeCAD::recompute. A different call would create a new pool of condition variables.

I do not understand anything about the GUI. But I think that the main thread's event loop can:
 * 1) Trigger the view providers that correspond to the threads that finish their processing; or
 * 2) Trigger the view providers of all threads when they have all finished processing.

But... ... Yes, there is a "but"!

Many threads need to access the result of the processing. For example, think of the variable Shape in Module Part. After set, this Shape should never change. Other threads might be accessing it. So, this Shape should be managed by a shared_ptr. No one should directly access this shared_ptr. But anyone can get a copy of this Shape. This copy is, in general, not supposed to be stored.

So, any function that needs to access the shape is supposed to acquire it first. And then, access the shape through this shared_ptr. When the shape is updated, an atomic shared_ptr::swap (in C++20 and before C++20) shall be used. Any thread working with the previous Shape will keep using the old Shape. The old shape will remain valid until the last function accessing it returns (in practice, the last shared_ptr is destructed).

Here too, it is important that any object is still functional even when it is removed from its owner. The object for being functional shall not depend on upward references being valid or not.

Already done (almost) like this!
FreeCAD already implements Part::TopoShape almost like this. It has a private TopoDS_Shape _Shape. No one tweaks an already set _Shape. What one can do is:
 * 1) Get a const reference to _Shape; and
 * 2) Set the _Shape (assignment).

To change this and make it use an atomic shared_ptr, we need two change those two methods and make:
 * 1) The private variable _Shape shall be a shared_ptr.
 * 2) The TopoShape::getShape shall return a temporary shared_ptr (a copy of _Shape). This shared_ptr shall not be stored!
 * 3) The TopoShape::setShape shall create a new TopoDS_Shape and atomically swap it with _Shape.

The only problems are:
 * 1) In a certain funciton, getShape shall not be called many times. It should be called once and the same local shared_ptr returned by the first (and only) call to getShape shall be used until the end of the scope.
 * 2) All 600 places where getShape is used need to be reviewed. The code that needs to be reviewed is to be adapted in a straight forward way, though.