Boost logo

Boost Users :

Subject: Re: [Boost-users] Signals2 benchmark
From: Gottlob Frege (gottlobfrege_at_[hidden])
Date: 2015-02-07 01:07:23


On Fri, Feb 6, 2015 at 8:11 PM, Joren Heit <jorenheit_at_[hidden]> wrote:
> 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<void()>;
> using Event2 = Signal<void(int)>;
>
> Emitter<Event1, Event2> 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


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net