On Fri, Feb 6, 2015 at 8:11 PM, Joren Heit
Thanks for the many responses and suggestions. However, I'm not sure if everything applies to my particular application. If it does, I don't think I fully understand...
My implementation provides an Emitter class (template), rather than a signal class. Signals are types which act as template parameters to the Emitter. For example:
using Event1 = Signal
; using Event2 = Signal ; Emitter
em; // normally, you'd probably derive from this em.connect<Event1>(someFunction); em.connect<Event2>(otherFunction); em.emit<Event1>(); em.emit<Event2>(42); Each Signal-type has its own corresponding vector of slots defined within the Emitter. Calling connect/disconnect adds/removes a slot to/from this vector and calling emit() results in iterating over it and calling all its slots (this is a read-only operation).
I can see trouble arising when thread 1 is iterating the vector while thread 2 is modifying it. Would it be an idea to have the emitting thread 1. lock the vector, 2. make a local copy, 3. unlock, and 4. iterate the copy? This way, the modifying thread only needs to wait for the copy being made instead of every slot being called and executed. Does that make sense at all?
Yeah, you are on the right track. Making a copy of data while holding a lock then processing the copy without the lock is often a good strategy in threading. However, in this case... int * somePtr = nullptr; void someFunction() { // can somePtr change after the check but before the set? if (somePtr) *somePtr = 17; } void cleanupPtr() { // this looks safe, but compilers and CPUs can reorder this code: int * tmp = somePtr; somePtr = null; delete tmp; } void thread1() { em.emit<Event1>(); } void thread2() { em.remove<Event1>(someFunction); // now safe to cleanup (?) cleanupPtr(); } Now lets say the emit and the remove are happening "at the same time", and - Thread1: emit gets to the lock first, makes a copy, and unlocks - Thread2: remove comes in, gets the lock, removes someFunction, returns - Thread1: calls someFunction as part of iterating over copy of list - Thread1: someFunction checks somePtr, sees it is non-null, great! (?) - Thread2: after returning from remove, calls cleanupPtr - Thread1: either writes to deleted memory, or writes to null Threading is fun!
Thanks again for all the help. Scott Meyers was right in his lectures about this being a helpful community! Oh, and if my formatting is screwed up, I'm truly sorry, but I'm writing this on my phone.
You are actually borderline not (or no longer) talking about boost, but your own code/implementation. Some might call that questionable for a boost list. But threading is fun! P.S. why write your own - why not use boost? Because of performance? Was that the original point? P.P.S. correctness is typically better than performance. I've written the world's (second) fastest square-root. It returns 17. Not very accurate, but very fast. Tony