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. A method like "GetShapeLock" or "AcquireShape" should return this copy.

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, na 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.