Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a double buffer technique for concurrent reading and writing?

I have a relatively simple case where:

  1. My program will be receiving updates via Websockets, and will be using these updates to update it's local state. These updates will be very small (usually < 1-1000 bytes JSON so < 1ms to de-serialize) but will be very frequent (up to ~1000/s).
  2. At the same time, the program will be reading/evaluating from this local state and outputs its results.
  3. Both of these tasks should run in parallel and will run for the duration for the program, i.e. never stop.
  4. Local state size is relatively small, so memory usage isn't a big concern.

The tricky part is that updates need to happen "atomically", so that it does not read from a local state that has for example, written only half of an update. The state is not constrained to using primitives and could contain arbitrary classes AFAICT atm, so I cannot solve it by something simple like using Interlocked atomic operations. I plan on running each task on its own thread, so a total of two threads in this case.

To achieve this goal I thought to use a double buffer technique, where:

  1. It keeps two copies of the state so one can be read from while the other is being written to.
  2. The threads could communicate which copy they are using by using a lock. i.e. Writer thread locks copy when writing to it; reader thread requests access to lock after it's done with current copy; writer thread sees that reader thread is using it so it switches to other copy.
  3. Writing thread keeps track of state updates it's done on the current copy so when it switches to the other copy it can "catch up".

That's the general gist of the idea, but the actual implementation will be a bit different of course.

I've tried to lookup whether this is a common solution but couldn't really find much info, so it's got me wondering things like:

  1. Is it viable, or am I missing something?
  2. Is there a better approach?
  3. Is it a common solution? If so what's it commonly referred to as?
  4. (bonus) Is there a good resource I could read up on for topics related to this?

Pretty much I feel I've run into a dead-end where I cannot find (because I don't know what to search for) much more resources and info to see if this approach is "good". I plan on writing this in .NET C#, but I assume the techniques and solutions could translate to any language. All insights appreciated.

like image 980
Buretto Avatar asked Jan 25 '23 08:01

Buretto


1 Answers

You actually need four buffers/objects. Two buffers/objects are owned by the reader, one by the writer, and one in the mailbox.

The reader -- each time he finishes a group of atomic operations on his newer object, he uses interlocked exchange to swap his older object handle (pointer or index doesn't matter) with the mailbox one. Then he looks at the newly obtained object and compares the sequence number to the object he just read (and is still holding) to find out which is newer.

The writer -- writes a complete copy of latest data into his object, then uses interlocked exchange to swap his newly written object with the mailbox one.

As you can see, the writer can steal the mailbox object at any time, but never the one that the reader is using, so read operations stay atomic. And the reader can steal the mailbox object at any time, but never the one the writer is using, so write operations stay atomic.

As long as the interlocked-exchange function produces the correct memory fence (release for the swap done in the writer thread, acquire for the reader thread), the objects can themselves be arbitrarily complex.

like image 101
Ben Voigt Avatar answered Jan 26 '23 21:01

Ben Voigt