zhaozheng
zhaozheng

Reputation: 81

thread safe with pop and visit in c++ queue

I know std::queue is not thread safe, but I don't want to lock queue. so I use pop & push limit on usages.

for instance, when I want to pop: I have an enum to express first element status

enum {
    Busy = 1,
    Unused,
}

when I add an elment to queue:

void UserAdd() {
    lock.lock();
    element.status = BUSY;
    queue.push_back(element);
    lock.unlock();
}

when I visit :

//only visit function, and every element only called once.
void UserVisit() {
    auto header = queue.front();
    .......
    queue.front().status = UNUSED;
    return ;
}

I judge first elements status when I want to pop element.

If first element is Busy, wait;

If first element is Unused, pop;

void UserPop() {
    while (queue.front().status != Unused) {
        usleep(200);
    }
    lock.lock();
    queue.pop();
    lock.unlock();
}

thread A: 1. UserAdd, 2. UserVisit, 1.UserAdd, 2.UserVisit loop ...

thread B: 1. UserPop.

Is UserPop() && UserVisit thread safe ?.

I think It's thread safe.

Upvotes: 3

Views: 1289

Answers (1)

jfMR
jfMR

Reputation: 24758

 No thread safety

Note that the member function pop() does modify the queue. If UserPop() is called by multiple threads concurrently, one thread may modify the queue by calling pop() on it at the same time another thread is reading the queue by calling front():

void UserPop() {
    while (queue.front().status != Unused) { // <-- read queue
        usleep(200);
    }
    queue.pop(); // <-- modify queue
}

Since the queue itself, std::queue, doesn't handle concurrent access for you, UserPop() is not thread-safe.


Make it thread safe

A simple approach to make it thread-safe is to add a mutex and hold a lock on it when reading or modifying the queue:

std::mutex mtx;

// ...

void UserPop() {
    std::unique_lock<std::mutex> lck(mtx);
    // mtx is locked at this point
    while (queue.front().status != Unused) {
        lck.unlock();
        usleep(200); // mtx is not locked
        lck.lock();
    }
    // mtx is locked at this point
    queue.pop();
}

std::queue's member functions front() and pop() above are always called while holding a lock on the mutex.

However, you may want to consider using std::condition_variable instead. It provides an optimization over the busy waiting:

std::mutex mtx;
std::condition_variable cv;

void UserPop() {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, [this]() { return queue.front().status == Unused; });
    queue.pop();
}

Upvotes: 3

Related Questions