GNU Radio Thread/Threat Creation
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
withscheduler_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 bythread_body_wrapper
andtpb_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 likestatic 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.