Since the single-threaded scheduler was recently deleted in GNU Radio’s next branch, we’re left with the multi-threaded version. I guess this change doesn’t hurt too much. The multi-threaded scheduler is the default and, therefore, already used by the vast majority of users.
This scheduler starts one thread per block to distribute the load and parallelize processing to benefit from modern multi-core CPUs.
To understand how GNU Radio works under the hood, I wanted to see how these threads are created. What I expected to find were threads that run a static function that takes a block as parameter and handle its execution. What I found was a bit more complicated.
The scheduler maintains a thread pool.
During startup it creates one
thread_body_wrapper per block.
This wrapper mainly masks signals like
Inside the wrapper, there is a
Actually, this should be the function, but the
tpb_container is a copy-able object that overwrites the
operator() to appear like a function.
Inside this overloaded operator, it instantiates a
tpb_thread_body, which isn’t a function, but a class that implements the actual functionality in its constructor.
tpb_thread_body doesn’t actually execute the block.
It has a member of type
block_executor, which takes care of running
So to execute
work() one just uses the
block_executor which is a member of
tpb_thread_body, which is inside a
tpb_container, which is inside a
thread_body_wrapper, which is a
boost::thread, collected in a
Easy as that…
I think this part of the code is not really accessible to potential contributors. In my opinion, we should try to reduce complexity as good as we can. Some initial thoughts:
- The scheduler is overly generic since it still contains code to select different schedulers.
I think we should delete all unnecessary complexity and merge
- We could transform
tpb_thread_bodyinto what it actually is, a static function run by the thread.
tpb_thread_bodybecomes a function, it can be directly used by
- It would be strange to have one file and one class only for a single static function. This function could, for example, be added to
block_executor, having something like
static void block_executor::run().
We’d end up with something like:
auto f = boost::bind(&block_executor::run, blocks[i], block_max_noutput_items); d_threads.create_thread(gr::thread::thread_body_wrapper<decltype(f)>(f, name.str()));
If you are interested, join the discussion.