What is the advantage of synchronizing robot I/O?

I have seen about a 50/50 split between teams with java code that wrap all calls to IO and motor controllers in a synchronized statement and not. What is the advantage of doing this? Doesn’t this block the main thread a lot?

Not sure I could cite an advantage across the board. If there was, I’d trust the WPI folks to have added it at their level.

One could analyze each case to see whether it makes sense.

However, I’d start at a higher design level: Why are multiple threads reading or writing the same IO point?

In general, there’s careful design needed when the order of code execution becomes non-deterministic. Careful analysis is needed to understand which code relies on output from other code, and ensuring assumed sequences of operation are always met. Failure to do so results in very weird and non-reproducible behavior… I’ve been burned by that more than once.

I’m not an advanced java developer by any means. But if I were reviewing someone’s robot code, I’d expect a nice fat comment block next to the synchronized statement explaining precisely why it’s required in that case.

I might guess that the desire is to make the “read all inputs” or “set all outputs” operation atomic. But, I don’t think synchronized is guaranteed to do that in all cases.

1 Like

If you’re not threading manually, then synchronized calls are unneccesary overhead. And I’d caution that threading manually is … somewhat painful. That’s why the new (for 2020) PIDController is not asynchronous, for example.

1 Like

Do you have some example projects where you have seen this?

1 Like

254 uses this in their periodicIO mechanism for example. (see Elevator.java from 2019 and Drive.java from 2018)

As far as I know - and anyone who knows better feel free to correct me - 254 implements their own way of scheduling tasks for the robot to complete, as opposed to using the WPILib built-in Command. Since they’re managing task scheduling themselves, and they’re choosing to multi-thread it, synchronized can be used to prevent race conditions (in the Elevator case, I can see them using stdout which iirc should be wrapped in a synchronized block or a synchronized method, as well as writing configs to their speed controllers).

If using WPILib’s built in scheduler, I don’t immediately see a need for synchronized blocks.

I am curious about this because my team basically runs critical code in the main thread via the WPILib scheduler, then we have various other “microservices” running in their own threads with various priority. So i’m wondering if this is something we should be looking in to.

The majority of my threading experience is in c-family languages, and I have only ever used synchronized statements when doing realtime rendering in Java

1 Like

This is what it looks like to me too. This is the only reason I can think of to use synchronized.

@ewpratten Can you link your code? It depends on if you have situations where there can be race conditions.

1 Like

Sure. (here is the core library)

I think the most common case of inter-thread interaction is in our logger. The codebase is pretty big, so its hard for me to give an easy-to-understand example without explaining a good chunk of our framework first.

Here is a bit of documentation on the logger for reference

We’ve had no issues* keeping everything in the main thread, including sensor reads and motor output writes. Because everything was on the same thread, there was no need for synchronized blocks or locking mutexes.

We still choose to read all inputs and write all outputs at once in separate methods (like team 254) because it’s cleaner and allows us to disable all motor outputs very easily (comment out everything in the writeOutputs() method of that subsystem).

* We did have an issue with the I2C REV Color Sensor blocking our thread by 0.543 seconds, so it was recommended by @Peter_Johnson to multithread that. However, we didn’t get around to doing so due to the cancellation of the season.

1 Like

I see a lot of teams doing this, and like it.

We still do hardware IO in each subsystem individually. Our statemachine utility handles if, and when to call each IO function, although some of our subsystems that are built abstractly enough to be used year-to-year are based around queues, which is more like reading and writing all at once.

1 Like

So yes… you are using threads. I can’t go through the entire library to make sure everything is thread safe but let’s just explore the RobotLogger that you pointed out:

The RobotLogger creates a Notifier that will periodically call the pushLogs function. pushLogs loops across the periodic buffer to write each string to the usb logger and then clears the buffer. This raises a few concurrency concerns to me:

  • What happens if the periods between pushLogs are too small? Can the code iterate across that for loop in multiple threads at the same time? Will that scramble the log?
  • What happens if one thread is in the notifier loop and another thread adds something to the buffer? Is the added thing: printed, not printed, scrambled, or is an exception thrown?
  • Is the periodic_buffer class (ArrayList) thread safe? What happens if 2 different threads call log at the same time?

This is just what I thought of for this example. Every time anyone has multiple threads I encourage them to ask questions like this of every single line of code. Any question that cannot be answered should be wrapped in a synchronized block (or use some other method of thread control) to make the answer explicit. This can be a lot of work which is why it might be easier to not use multiple threads - not using multiple threads also means it can be harder to produce incorrect code (yes, incorrect code is a thing).

To answer your initial question though, for all of the components in WPILib, you do not need to use multiple threads for the best performance (actually, you might find that more threads might mean less performant code).

5 Likes