Atomic operations in Spin
r.daneel
Posts: 96
I am sharing a long variable between cogs. I didn't use a semaphore to ensure consistency because I was careful to perform atomic operations on the variable - or so I thought. I use |= and &= to update the value in several places in code that runs in different cogs, and I was seeing some inconsistencies in the value of the variable. I eventually solved the problem by wrapping the |= and $= in lockset and lockclr pairs. I think my assumption that |=, $= (etc) are atomic operations was wrong - and that makes sense upon thinking about it for longer than I did initially. I suspect the ! assignment operator is similarly not atomic. The use of sempahores around the updates to the variable has fixed the problem, but it's hard to know if that was because of my assumptions above or whether it just changed a timing problem and made it more obscure.
So, the question: Is my (newly formed) assumption that operators like |= and &= (and !x) are not atomic correct?
So, the question: Is my (newly formed) assumption that operators like |= and &= (and !x) are not atomic correct?
Comments
:= is not atomic?
That depends. If the target appears on both sides of the assignment operator, then no. The right side would require a read, the left side would do a write -- that's [at least] two hubops for the same variable.
Right, so a := b is atomic. That was my point... and what led me to my mistake in thinking (eg) a |= b was atomic. I should have thought harder about it - it just didn't occur to me...
Yep, a |= b is, of course, shorthand for a := a | b
Yeah, I know. And you'd think I would have thought about that, especioally given the number of years I've been programming. Just didn't occur to me...
The underlying question here what exactly are you trying to do? Do you actually need atomic operations?
There has been much debate about this here over the years. It turns out that most Prop programs that share data between COGs do not use locks.
Typically one COG is a producer of data and another is a consumer. For example a UART running in a COG delivering bytes to an application via a cyclic buffer. No locks are required for this.
Similarly one cog issuing commands to a second through some "mailbox" and waiting for the response. No locks required.
Or perhaps a single COG producing information that is to be read by many others.
Locks are required if there are multiple readers and writers of some data. For example four COGs tying to add to a running total of something. This kind of thing does not seem be be done very often. And perhaps there are ways around it. In this case for example one could have each COG maintain it's own sub total and the grand total would be obtained from summing the four subtotals when required.
So what are you up to?
Well, I have several projects, but the one in question is a Stingray. It has 3 propellers on it which communicate via a version of Beau's fast prop-prop communication code. One of the props (the monarch) does things like boot the other props and load new versions of their software as required (code for all props is stored and updated on a bank of 4, 1MBit EEPROMs connected to the monarch), as well as (at the moment) drive the wheels (eventually there will be a separate prop for locomotion, but I haven't built that board yet). A second prop has all the sensors connected to it - I have 5 Pings on servos, each with a Sharp IR sensor mounted above the Ping. The servos scan so that the Pings and IR sensors get an overlapped 360degree view. (And before anyone asks, just because I wanted to see if I could make it work). Also attached to the sensor prop is a 9-axis IMU, a thermometer, RTC and a GPS. The third prop drives a parallel LCD touchscreen that shows the Stingray's "view" and sensor data (and will eventually allow for user intervention). I wasn't planning to devote a prop to the display, but I ended up with a parallel display instead of serial, and that takes 22 pins to drive, so I don't do much else on that prop. The display does have a micro-SD slot on it, and I have enough spare pins to drive that, so maybe one day I'll add storage capability there. Once I get the sensors up and running properly I'll add a locomotion prop to take care of driving the wheels and basic object avoidance. It's a slow process - I don't have a lot of spare time and I do this in fits and spurts.
Once I have the basic functionality described above in place I plan to add a neural network (perhaps using multiple props and maybe lots of SRAM) that will hopefully one day help the Stingray to learn about its environment. That's a way off though, but getting closer. That's where my real research interest is, but I've had to teach myself as much as I need to know about electronics and microcontrollers, and sensors etc., to get the thing built (my background is software, not hardware. I really wish I could figure out how to solder those surface mount chips, but that's beyond my ability at the moment). It's been a steep and somewhat slow learning curve, but a lot of fun along the way.
So, after all that - the variable in question records status - for example, the various cogs that handle the different sensors on the sensor prop will all update the status variable by ORing in bits to indicate the status of the sensors they own, and that status variable gets shunted around the communication ring to other props so the status of all sensors is known to all props.
And I'm a little embarrassed that it didn't occur to me that |= (and &=) wasn't going to be atomic! Actually I didn't really think about it - had I given it more than a second or two of thought I would have realised. I'm surprised I got away with it for as long as I did before it started causing problems.
1) Have four bytes. One for the status bits of each Propeller.
2) Each of those bytes can be updated independently. No locks required.
3) Those bytes are arranged to be consecutive starting at an address that is a multiple of 4.
4) Those four bytes can then be read as a long which is then your combined status.
5) In this way each byte has a single producer (writer) and a single consumer (reader).
Of course that dictates you have a maximum of four units to monitor and a maximum of 8 status bits per unit.
Perhaps wrapping a lock around all this is just as easy. Just a thought.
That's true, but only if b is modifiable by another cog. In my case b is actually a constant, so no problem. (I realised I should have noted that immediately after I posted it...)