1

So I'm working on a growing C program in a Linux / POSIX environment, and I've run into an area where I'm not quite sure how to proceed.

Basically, I'm using a module pattern to develop my code for use with different external hardware and I/O operations (for example: a file module, a camera module, a Serial module, etc.) Some of these operations take a bit of time to complete, and should be placed in their own thread for obvious reasons.

My question basically boils down to this: Should the modules themselves control threading on internal operations, or should the calling code "above" each module implement threading on the raw functions themselves.

On one side, I can see from a complexity point of view that having to control threading of multiple modules from a central point can be a bit of a mess. However, if the module implements threading on it's own you lose the context and control of individual processes. A compromise could be exposing the thread objects via the interface header file, but then I feel you might be back to the first step again.

The modules are currently being designed as an abstract functional block of code, with little exposed outside of configuration variables (so it would be a generic "Camera" interface with different modules implementing the hardware of that specific build or revision). IO operations aren't intensive, but they tend to take a while to complete; so a possibility could be using an exposed bool/enum variable to indicate status?

  • 2
    Apart from the threading concern, how interchangeable should the modules be? Is the calling code interacting with a generic module interface, or with a "usb-connected brand X camera" module? – Bart van Ingen Schenau Sep 27 '17 at 7:11
  • 2
    I am not sure we have enough informations, on the worst case, where your application is very IO & Threading intensive, you may need to delegate the Threading to a ThreadPoolManager in order to put a limit to how much concurrent operation you allow. Calls to that PoolManager would be for me an implementation detail. And I would design the API of each module to be asynchrounous based on callbacks, even if the implementation could be synchronous. – Walfrat Sep 27 '17 at 8:38
  • 1
    I think each module should be a thread (as @Walfrat said, if I understood properly), and your overall process acts like a 'server'. You might have to limit how many modules run at one time, using a counting semaphore. I created a database transaction server (like a web server, using HTTP for requests / replies) and had a main process that listened on a TCP port and spawned (a limited number of) threads which then ran to completion on their own. Perhaps this model would work for your situation? – user251748 Sep 27 '17 at 16:41
  • Thanks @Walfrat I've updated my post with some more info. I'm definitely trending towards the module being an asynchronous and having some sort of controller in the overall thread. – au42 Sep 27 '17 at 17:37
  • 1
    As an alternative to consider, take a look at the async pattern. Asynchronous programming is relatively robust to the underlying thread implementation, and may be effective in making the issue of threading a "don't care." – Cort Ammon Sep 28 '17 at 22:40
1

The best option is to use both approaches but be very cognizant of ensuring threads are only known to their owner.

The goal for each module is:

  • Each module should be usable stand-alone.
  • Each module should not care what other modules are using it.
  • Each module should make it as easy as possible to be used correctly. Which means it shouldn't rely on the caller to use it correctly. It should provide an interface that makes it really hard to use incorrectly.

The easiest way to achieve these bullet points is to make the module's API blocking and synchronous. This doesn't mean that the module should not use threading or have threading managed externally. It means if the module needs to use internal threading behind the API then keep it hidden from the API user and make the function call appear synchronous to the user.

Blocking and synchronous works most of the time but certainly not always. In the cases where calls take too long or synchronous is not a good option then making the module's API asynchronous may be a good choice. With that said, this adds complexity to the calling code so even if an API has calls that might take a long time, it may still be better to leave the interface blocking if the caller is already managing the higher level operations that call the interface.

Speaking of higher level operations, the problem with just focusing on how the module API's incorporate threading is that module operations tend to be just a part of a more complete system operation. Thus, the necessity arises that the calling code will also have to manage its own threading to coordinate long operation processing. In order to achieve seamless integration, it is very important that module threads are unknown to the caller/higher level code and likewise the modules know nothing of the caller's threads.

  • I took a few days to think about this and jot down my needs, coming back here I believe this is a great overview of what I need to look out for. I was hesitant to use module threads hidden from the calling code because of the lack of control, but your explanation makes much more sense on why I should keep the API of each module more simple and predictable. Thanks! – au42 Sep 29 '17 at 22:12
3

Most likely, you'll want the main program to manage the threads and each module to just perform its function. To do this, you'll need to define an API that the modules must all support. The main program would call a function from this API when starting the thread, maybe a second to clean-up after the thread is done or canceled, and a third that reports back the progress of the thread, so the main program can display a progress meter.

The easiest way to be sure is to stub out two of the modules assuming the modules will do all the thread management. Any code you copy/paste from the first module when writing the second is a good candidate to be refactored into the thread manager in the main program.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.