GNU Radio

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 SIGINT.

Inside the wrapper, there is a tpb_container. 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.

But even tpb_thread_body doesn’t actually execute the block. It has a member of type block_executor, which takes care of running work().

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 thread_pool. 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 scheduler with scheduler_tpb.
  • We could transform tpb_thread_body into what it actually is, a static function run by the thread.
  • When tpb_thread_body becomes a function, it can be directly used by thread_body_wrapper and tpb_container can go.
  • 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.