Locks with PASM
JohnR2010
Posts: 431
in Propeller 1
I sheepishly stand in your office doorway, looking at the floor, knowing full well I should know the answer to this but I’m just not 100% sure.
Say I have two PASM apps sharing the same long in main memory. Do I need to use locks to make sure they have exclusive access to the long as each cog independently reads and writes to it with RDLONG and WRLONG? I know if this was all SPINN code I don’t need locks for access to a single byte, word, or long. Just not sure if the same is true for PASM.
I have some weird behavior and just trying to track it down. I have tried with and without locks and no change. After rereading the lock section of the manual for the 3rd time I’m pretty sure I don’t need them if I’m accessing a single long, both in PASM and SPINN.
Thanks
Say I have two PASM apps sharing the same long in main memory. Do I need to use locks to make sure they have exclusive access to the long as each cog independently reads and writes to it with RDLONG and WRLONG? I know if this was all SPINN code I don’t need locks for access to a single byte, word, or long. Just not sure if the same is true for PASM.
I have some weird behavior and just trying to track it down. I have tried with and without locks and no change. After rereading the lock section of the manual for the 3rd time I’m pretty sure I don’t need them if I’m accessing a single long, both in PASM and SPINN.
Thanks
Comments
For instance, if cog a writes data from cog B and cog C to an LCD locks could be used so that only one of the cogs has access to the variable cog A writes to the LCD at any one time.
-Phil
What are you doing standing there boy? What, cat got your tongue? Come in. And close the door behind you.
Don't you know it's bad manners to disturb the staff during their afternoon nap... er...tea break. Bend over and take three strokes of my new cane.
Now, what seems to be the problem? Stop sniveling boy, speak up.
What's that? Your having problems with atomic operations and locks in the Propeller? You were sleeping in class again weren't you boy? You have been warned about that. Bend over and take another three strokes.
It's simple, pay attention boy, reading and writing bytes, words and longs to hub memory are each atomic operations and don't need any locks. Of course you had better be sure the reader and writer are exchanging the same type else you will be in trouble and it's detention again for you.
That's good enough if you are only communicating simple values between two COGs. But what if you want them synchronized, to be sure the reader sees every change the writer makes? Then you had better have a flag that is set TRUE by the writer when it writes and set FALSE by the reader when it reads. The writer never writes if the flag is TRUE, the reader never reads if the flag is FALSE. That way they stay in sync. Get that wrong boy and it's another three strokes of the cane and possibly expulsion.
Of course that means that the reader and writer block on every exchange. That can be eased by using a FIFO between reader and writer. That's probably a bit complex for you boy, but if you are interested Master Chip has a fine example in his FullDuplexSerial module. Go to the OBEX and look it up.
Of course if you have such a flag or FIFO in place for exchanging messages between two COGs you don't need any locks.
What? Speak up boy, stop mumbling. You have more than one reader and/or one writer and you are exchanging complex data structures between them? Unheard of impertinence. You are getting too big for your boots boy. Bend over, take another three strokes.
Now, get out of here and leave us to our nap...er...tea break. Before I reach for my cane again. And close the door behind you!
....
Seriously though. One rarely needs locks in Propeller projects. If there is only one reader and one writer for any particular piece of data. I wager there is not one example in OBEX.
Oh, and that is pretty much how our teachers used to interact with us when I was a kid.
I been banging my head on the wall all weekend. Found the problem just after I hit post. Had nothing to do with locks. I often find after I take the time to clearly communicate the problem to you guys, the answer comes on its own.
Thanks Mr. Teacher!
Sorry, could not resist it. Glad you appreciate it. Looks like you have the idea down now.
As is often the case, by the time you can articulate the problem your brain has figured out the answer in the background!
Interestingly locks are so rarely used in Propeller projects that Chip was asking if he could leave them out of the P2 design a while back. As Mike says they are only needed when there are more than one reader or writer to a data area that is supposed to be consistent.
Luckily the locks stayed in the P2, else my multi-core FFT would never work.
Suppose you have a byte in the hub that tracks the state of multiple processes. Each bit in the byte is a flag of some sort. If process A wants to update the byte by changing one or more bits, it has to read the byte, modify the bits, and write it back. Now, suppose, between reading the byte and writing it back, process B needs to do the same. So it reads the byte, then process A writes its new value while process B is modifying its bits in cog memory prior writing it back. But what gets written back by process B completely ignores that changes that A made. That's why each process has to set a lock before reading the byte and to keep it set until after writing the byte.
There are cases where two cogs write the same hub data where locks are not necessary. A common one is this:
A Spin cog does a remote procedure call to a PASM cog by writing parameters to the hub, then sets a byte telling the PASM cog what to do with the data. It then waits for the command byte to be cleared before accessing the answer from another location. The PASM hub sits in a loop, waiting for the command byte to be set. Once it sees a non-zero command, it reads the parameters, does its calculations, then writes the answer back to the hub. After doing so, it clears the command byte, telling the "calling" process that it has finished.
Now, there's even an exception to the above example. If the PASM cog services more than one other Spin cog, the Spin cogs will have to use a lock to keep from trashing the common command/parameter/result hub area.
-Phil
Locks also provide a good mechanism for handling things like an i2c buss that needs to be hammered from different sides of the wall.
What fostered this question is, I read someplace that the PASM command WRLONG takes 8 to 24 clock cycles to complete. I got off on a tangent thinking each clock cycle wrote only one byte of the long as main memory is addressable at the byte level. If two cogs were writing to the long at the same time there could be a collision. But instead what I guess is happening, is when the hub gives a cog access to main memory it must allow it to transfer a full long before the next COG is given access??
I had a single long in memory I was using by all my cogs as a debug register. The value was getting corrupted and for the life of me I couldn’t figure out why. So, I started down this road of needing a lock to keep two cogs from writing to it at the same time. Turns out the long wasn’t getting corrupted it was just being clocked out faster than my logging terminal could display it. Found the bug in my logging terminal and she is smoking now.
I think I have a good grasp on the need for locks. What I don’t have my head around is how data is moved in and out of a cog when it has access to cog memory. Thanks.
-Phil
But, anyway, if you ever need more than eight, the master/slave approach is still available.
-Phil
determine who is in control of the variable at each stage, and the control is passed on explicitly.
For instance a "go" flag for a cog (call it A) might be only allowed to be set by the client cog (call it
when the flag is zero. And only A is allowed to clear the flag when it is non-zero. In other words
the flag being zero means only B can write it, and the flag being non-zero means only A can write
it (and only with zero).
This sort of handshake is quite limited, and you have to be careful to stick to the rules to avoid the
need for a lock.
For an interesting generalization of this sort of communication by a flag see the 100 prisoners/light bulb
puzzle https://cut-the-knot.org/Probability/LightBulbs.shtml
That makes sense. From what I have observed I thought it could transfer a full long before going on to the next COG. Thanks.
you can rely on code like:
The examples I've found say to do this:
The problem that I found was that it appeared - and I use that word deliberately - that one of the cogs was able to "hog" the lock while it did its processing. In other words, one of the cogs managed to be the only cog to actually obtain the lock; the other cogs couldn't obtain the lock because each time their "slice" came around the other cog already obtained the lock.
I suspect it's because of the round-robin hub access and a matter of timing. The issue was extremely sporadic but I was able to fix the issue by having each cog wait a different amount of time before reattempting to obtain the lock:
Comments? I'm probably all wet on this, I'm sure someone will point out a basic flaw in my premise.
Walter
I think that's right.
However with 3 or more cogs competing you have the issue of the sequencing of hub ops being round-robin
like this - with more than one cog waiting how to guarantee fair access...
Often the way fair-access gets implemented is to use a spinlock (like the Propeller locks) to control access to a more
complicated queue/lock structure. You ensure that the time you hold the spinlock for is small. The main queue/lock
controls access to the resource, the spinlock controls operations on the main lock.
Locks should be acquired and released as quickly as possible. Just long enough to read whatever is coming in and/or write whatever is going out. The processing part should be done when not holding the lock.
As noted above, if there is only one reader and one writer of a shared item locks are not required at all so the problem cannot arise.
Is there even an object in OBEX that uses locks?
EDIT: I also use a lock in the threaded chess program. One cog writes a set of chess moves onto a queue, and then each cog pops moves off the queue until it is empty.
EDIT2: PropGCC allocates a single lock when a program first start up. The lock is used to implement mutex locks and other thread-safe functions.
EDIT3: Here's a list of 29 OBEX objects that I found by grep'ping for lockset. Grep only works on 8-bit ASCII files, so this list does not include any 16-bit UNICODE files.
1Mbaud FullDuplexSerial (Fixed baud-rate)/asm_write_ex.spin: repeat while (lockset(lockId))
3-Axis CNC Control Package/SD-MMC_FATEngine.spin: repeat while(lockset(cardLockID - 1))
640 x 480 VGA Tile Map Driver w_ Mouse Cursor/VGA64_TMPEngine.spin: repeat while(lockset(lockNumber - 1))
74C922 Keypad Driver/74c922buffer.spin:repeat until not lockset(SemID) ' Lets lock the memory
74C92X Keypad Buffer and Driver/74c92Xbuffer.spin:repeat until not lockset(SemID) ' Lock it
Combo PS2 Keyboard and Mouse Driver/PS2_HIDEngine.spin: repeat while(lockset(keyboardLockNumber - 1))
DS1307 RTC Driver/DS1307_RTCEngine.spin: repeat while(lockset(lockNumber - 1))
FAT16_32 Full File System Driver/Full File System Driver with DS1302 RTC/DS1302_SD-MMC_FATEngine.spin: repeat while(lockset(cardLockID - 1))
Full Duplex Serial Port Driver/Full-Duplex_COMEngine.spin: while(lockset(lockNumber - 1))
Generic I2C EEPROM Driver/I2C_ROMEngine.spin: repeat while(lockset(lockNumber - 1))
ILI9325 320x240 TFT driver/touchSPI.spin:select lockset lock wc ' try to claim spi lock
IR Kit/ir_reader_nec.spin: repeat while lockset(lock)
KISS WAV Player Driver/SD-MMC_FATEngine.spin: repeat while(lockset(cardLockID - 1))
KISS WAV Recorder Driver/SD-MMC_FATEngine.spin: repeat while(lockset(cardLockID - 1))
Lock-Bit Demo/simple_multicore_demo3d.spin: repeat until lockset(LockID) == False 'Wait in this loop for lock to open
Nordic nRF2401 Rf Tranceiver Handler/TRF24G.spin: repeat until not lockset( rf_sem )
Octal Button Debouncer/D8C_BUTEngine.spin: repeat while(lockset(lockNumber - 1))
PROPSHELL/PROPSHELL-master/Full-Duplex_COMEngine.spin: while(lockset(lockNumber - 1))
Propeller Backpack TV Overlay/prop_backpack_tv_overlay2.spin: repeat while lockset(timelock)
Pulsadis detector/pulsadis dual processor for Obex/Full-Duplex_COMEngine.spin: while(lockset(lockNumber - 1))
SYSLOG - Multicog debug_log to SD_Serial/DS1307_RTCEngine.spin: repeat while(lockset(lockNumber - 1))
Servos and Encoders Calibration/SD-MMC_FATEngine.spin: repeat while(lockset(cardLockID - 1))
Settings/driver_socket.spin: repeat while NOT lockset(SocketLockid)
Switch Debounce/DebounceCog.c: lockset(SemID);
Trending Barometer/I2C_ROMEngine.spin: repeat while(lockset(lockNumber - 1))
Wheel_Controller/Wheel_Controller.spin: repeat until not lockset(mutex_id)
Wiimote IR blob tracking camera/wiicamera.spin: add clockset,clkpin_ 'Set up clock
mdb_RealTimeClock/MDB_RealTimeClock.spin: repeat until not LockSet(gLockId)
uSDPropLoader/DS1307_RTCEngine.spin: repeat while(lockset(lockNumber - 1))
Yes, agreed. And I was holding the lock for the shortest time possible in each of the cogs. And there were different processing times between the LOCKCLR and the next LOCKSET in each cog. Which I thought would prevent this from occurring.
But apparently there was some pattern that the 3 cogs fell into that caused the cog (or cogs) to hog the lock. I think at least part of the issue is that each cog spun on the LOCKSET instruction until the lock was acquired.
Given that locks area HUB resource and given that HUB resources are accessed in a round the roundabout fashion, I would expect that when any COG releases a lock the next COG around the roundabout can always aquire it.
@Dave Hein
That is quite some list. Good job Chip was talked out of removibg locks for the P2 !
The Propeller documentation states that only one cog at a time can execute a LOCKxxx instruction.
Does this imply that they take 2 or less ticks to execute (which allows for the next cog to execute their LOCKxxx), or is there an internal locking mechanism that prevents the execution of future LOCKxxx instructions until the current instruction has completed?
I ask this because the manual shows the execution of the hub instruction spanning a total of 8 ticks (4 cog access times). I'm referring to Figure 1-3: Cog-Hub Interaction - Best Case Scenario.
It takes 8 ticks (from the point that the hub is sync'd) to execute a hub instruction.
This implies that there is a certain amount of decode/setup/etc. time before the hub instruction affects anything.
So, is the bit set on the first, second, ..., or eighth tick?
I like to think that LOCKS have the same atomic nature as any WRLONG. If one COG gets or releases a LOCK the next COG around the HUB cycle sees that as such.
Otherwise we have chaos.
Let's say one COG acquires a lock, spends a long, long time doing whatever. Then releases and reacquires the LOCK as fast it possibly can.
Then is it possible for that activity to block out another COG that is doing the normal, fast as possible, spin until LOCK, do minimal update to shared data and then release?
Somebody around here recently said they had to put random "back off" times in the acquisition of locks to prevent that happening. I just don't see how it can happen in the first place.
-Phil
Can anyone produce a simple program where:
a) One or more COGS repeatedly acquire and release a lock.
b) They manage to block the progress of another COG trying to acquire the same lock.
c) Each COG flashes an LED to indicate it's progress.
d) All COGS running PASM of course.