Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

signal and unlock order

Tags:

void WorkHandler::addWork(Work* w){     printf("WorkHandler::insertWork Thread, insertWork locking \n");     lock();     printf("WorkHandler::insertWork Locked, and inserting into queue \n");     m_workQueue.push(w);     signal();     unLock(); } 

I followed a tutorial and I got this. I was wondering if it is ok to change the order of singal() and unLock() like this

void WorkHandler::addWork(Work* w){     printf("WorkHandler::insertWork Thread, insertWork locking \n");     lock();     printf("WorkHandler::insertWork Locked, and inserting into queue \n");     m_workQueue.push(w);     unLock();     signal(); } 

If I can't do this, could you please give me details why I am not allowed to do this? Thanks in advance.

like image 933
user800799 Avatar asked Jun 21 '11 00:06

user800799


1 Answers

First, there is no correctness issue here. Either order will work. Recall that whenever you use condition variables, you must loop on a predicate while waiting:

pthread_mutex_lock(mutex); while (!predicate)   pthread_cond_wait(cvar); pthread_mutex_unlock(mutex); 

By signalling after the unlock, you don't introduce any correctness issues; the thread is still guaranteed to wake up, and the worst case is another wakeup comes first - at which point it sees the predicate becomes true and proceeds.

However, there are two possible performance issues that can come up.

  • "Hurry up and wait". Basically, if you signal while the lock is held, the other thread still needs to wait until the mutex is available. Many pthreads implementations will, instead of waking up the other thread, simply move it to the wait queue of the mutex, saving an unnecessary wakeup->wait cycle. In some cases, however this is unimplemented or unavailable, leading to a potential spurious context switch or IPI.
  • Spurious wakeups. If you signal after the unlock, it's possible for another thread to issue another wakeup. Consider the following scenario:

    1. Thread A starts waiting for items to be added to a threadsafe queue.
    2. Thread B inserts an item on the queue. After unlocking the queue, but before it issues the signal, a context switch occurs.
    3. Thread C inserts an item on the queue, and issues the cvar signal.
    4. Thread A wakes up, and processes both items. It then goes back to waiting on the queue.
    5. Thread B resumes, and signals the cvar.
    6. Thread A wakes up, then immediately goes back to sleep, because the queue is empty.

    As you can see, this can introduce a spurious wakeup, which might waste some CPU time.

Personally, I don't think it's worth worrying too much about it either way. You don't often know offhand whether your implementation supports moving waiters from the condition variable to the mutex wait queue, which is the only real criterion you could use to decide which to use.

My gut feeling would be that, if I had to choose, signalling after the unlock is marginally less likely to introduce an inefficiency, as the inefficiency requires a three-thread race, rather than a two-thread race for the "hurry up and wait" condition. However, this is not really worth worrying about, unless benchmarks show too much context switch overhead or something.

like image 68
bdonlan Avatar answered Sep 18 '22 18:09

bdonlan