0

I implemented a queue with linked list, and I want multiple threads to push elements in the correct order. I used mutex to lock my member functions, but the numbers printed to terminal is still not in correct order, which should be 1,2,3,4,5,6. Insteads it prints a wrong order, such as 3,1,4,2,5,6. This means the mutex lock DIDNT work. Can someone please tell me why? And how to fix this?

// Implement a queue<int>,need functions: push_front, back, pop_back, and print
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

struct ListNode { 
    ListNode* next;
    int val;
    ListNode(int x) {
        val = x;
        this->next = nullptr;
    }
};

class Queue {
    
public:
    Queue() {
        head = new ListNode(0); // dummy head
        tail = head;
    }

    
    void push_front(int x) { // add node at then end of linked list
        lock_guard<mutex> lock(m);
        tail->next = new ListNode(x);
        tail = tail->next;
        
    }

    int pop_back() { // remove the first node of the linked list
        lock_guard<mutex> lock(m);
        ListNode* back = head->next;
        if (back == nullptr) {
            throw invalid_argument("empty queue");
        }
        head->next = back->next;
        back->next = nullptr;
        return back->val;
    }

    void print() {
        lock_guard<mutex> lock(m);
        ListNode* p = head->next;
        while (p != nullptr) {
            cout << p->val << endl;
            p = p->next;
        }
    }

private:
    mutable mutex m;
    // Implement with linked list
    ListNode* head;
    ListNode* tail;
};


int main() {
    Queue* queue = new Queue();

    // Multiple threads push at the same time --> order is wrong
    thread t1 = thread(&Queue::push_front, queue, 1);
    thread t2 = thread(&Queue::push_front, queue, 2);
    thread t3 = thread(&Queue::push_front, queue, 3);
    thread t4 = thread(&Queue::push_front, queue, 4);
    thread t5 = thread(&Queue::push_front, queue, 5);
    thread t6 = thread(&Queue::push_front, queue, 6);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
    t6.join();

    queue->print();
}
  • 3
    Mutex will not guarantee an order. It will merely ensure that no simultaneous adds to the queue will stomp (happen at the same time as and potentially damage a shared variable) another. – user4581301 Mar 02 '21 at 17:10
  • 2
    Which one of the threads `t1` to `t6` is scheduled 1st is up to chance. No point checking anything else. – Richard Critten Mar 02 '21 at 17:10
  • Unrelated: There is no need for `queue` to be dynamically allocated. Consider replacing `Queue* queue = new Queue();` with `Queue queue;`, `thread(&Queue::push_front, queue, 1);` (and friends) with `thread(&Queue::push_front, &queue, 1);` to supply the address of the queue and `queue->print();` with `queue.print();`. In general, [Why should C++ programmers minimize use of 'new'?](https://stackoverflow.com/questions/6500313) – user4581301 Mar 02 '21 at 17:15
  • Note that if you want the push_front() operations to be performed in a strict order, there's no need to spawn any threads; instead you can just call push_front() directly from the main thread. Being able to do things asynchronously (and thus not in any fixed order) is the main reason for using threads. – Jeremy Friesner Mar 02 '21 at 20:15

1 Answers1

0

As many users mentioned, your assumptions about the meaning of mutexes is at least wrong in terms of (thread-) order. Order in general in the context of multi-threading is a quality, that almost always arises systematically from your business logic and fitting data types, not from single compiler/language intrinsics solely.

You can either solve your problem with a simple single threaded consecutive approach or, depending on your further purposes, you can change your underlying data type to a type that supports partial ordering (in the sense of an std::set for instance) in combination with parallel write access (without referring to one single container mutex solely, reducing the whole parallel approach to absurdity). Some things are possible here but it really depends on your actual concrete requirements.

Secundi
  • 804
  • 1
  • 1
  • 7