While trying to integrate two complex marketplace assets, I came across an issue, where two events were overlapping, and manipulating the same state at the same time, causing the state to get “corrupted”. I hadn’t realized until then, that events in blueprint really do run concurrently, even if running on a single-thread, and so those “multi-threading” issues can happen in blueprint events too.
Searching for “locking” resulted in 35 pages of hits in the forum; I gave up after 10 pages, as none of this seemed to have anything to do with what I was looking for. I also researched “critical section”, but there seems to be a C++ “critical section” object used in unreal for “real” multi-threading, so all the hits where about that.
I’ve made a basic example reproducing the issue, and the “solution” I came up with, to solve the problem. I’d like to know, if I my solution is actually “safe”, and if there is a better/more lightweight alternative to it, as I will have to use this everywhere where “shared state” is manipulated non-atomically, now that I realized how “unsafe” events in blueprints are. The last thing I want, is to regularly waste hours/days debugging concurrency issues.
So, here we go. First, the “broken” example:
Now, the “Working Example”:
Basically, what I do here, is I have an integer “lock”, and before I start to access the shared state, I increment it. I have assumed, that at least the increment node would be “atomic”, and if two events try to increment the lock at the same time, only one will get “1” as a result, and the other will get “2”. IF that is correct, than I think this solution will work. So, after incrementing the “lock”, the event checks the result of the increment, and can tell if they “got” the lock (result is 1). In that case, they proceed, and decrement when done. If increments (lock) and decrements (unlock) ALWAYS match, then the lock will never be 0 (free) again, until the event that first got “1” decrements it, so during all that time, no other event will enter the “critical section”. The order of the decrements between the events should not mater, and the decrement return values are not checked. If the increment (lock), returns more than 1, than the lock is already locked, and so the event must wait. It still must decrement anyway, wait a bit, and then retry. On the one hand, you want the waiting event to proceed as soon as the lock is free, but OTOH, you don’t want to waste masses of CPU on tight busy wait loops.
So, I’m hoping there is a more elegant/performant solution (like a true atomic compare-and-set), possibly coded in C++ (I haven’t learned to use C++ in unreal yet).