jeffreyveon
jeffreyveon

Reputation: 13820

Grabbing a lock that's valid only once in C++

I'm using C++ 11, where I have 4 threads that are parallely inserting into a concurrent queue. I know when the threads are done processing, i.e. the expected final size of the queue.

Now, I want to perform a final aggregation operation on the contents of the queue that should be strictly performed only once. For example, say I want to aggregate the values and POST it to an external service.

How do I acquire a lock which is valid only once? I can't use a simple mutex because that would not guarantee me the only once requirement.

Pseudocode:

// inside a thread
enqueue items to concurrent_queue

if(concurrent_queue.size() == EXPECTED_SIZE) {
    // do something ONLY once
}

Upvotes: 2

Views: 394

Answers (3)

AdamF
AdamF

Reputation: 2601

Check approach from Singleton implementations. The most basic:

bool executed = false;
void Execute() 
{
    Lock lock;      // scope-based lock, released automatically when the function returns
    if (executed == false) 
    {
       executed = true;
       //.. do computation
    }
}

Check also solutions with atomic variables and double-checked locking for better performance: http://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/

Upvotes: 2

Casey
Casey

Reputation: 42554

If you aren't concerned about the possibility of the operation failing, you can simply use an atomic<bool> that is associated with the operation. All of the threads will try to change the flag from false to true with compare_exchange_strong, but only one will succeed:

// inside a thread
enqueue items to concurrent_queue

if(concurrent_queue.size() == EXPECTED_SIZE) {
    std::atomic<bool>& flag = retrieve_the_associated_bool();
    bool expected = false;
    if (flag.compare_exchange_strong(expected, true)) {
      // do something
    }
}

If the operation can fail, you should use std::call_once and a std::once_flag associated with the operation. The other threads will wait while one tries to "do something", and each try in turn until one succeeds:

// inside a thread
enqueue items to concurrent_queue

if(concurrent_queue.size() == EXPECTED_SIZE) {
    std::once_flag& flag = retrieve_the_associated_once_flag();
    std::call_once(flag, []{
        // do something
    });
    // do something ONLY once
}

Upvotes: 2

Surt
Surt

Reputation: 16089

A simple solution.

if(concurrent_queue.size() == EXPECTED_SIZE) {
    // do something ONLY once
    static bool doItOnce = DoItOnceOnly();
}

Upvotes: 7

Related Questions