2023阿里云面试题——实现一个生产者消费者模型,使用无锁循环数组/队列(C++)

这个面试题的重点:
1、循环数组:元素在放入数组时需要模数组的长度来确认放入位置,由此判断队列是空是满的条件就不一样了。

  数组有指针head和tail分别指向队头和队尾,head指向第一个元素,队尾指向最后一个元素。

  一开始数组为空,head = tail,就可以判定消费者无法取得元素;

  如果数组满了,那么(tail + 1) % array.length = head,此时生产者无法放入元素。

2、无锁:那就不能使用std::mutexstd::unique_lock之类的结构,只能用std::atomic,但是std::atomic只有针对基本数据类型才有自加自减的原子操作,所以得用原子交换来实现head、tail指针的替换,比如compare_exchange_strong()compare_exchange_weak()。weak相比strong的话可能会有两值比较相等但依旧返回false的情况,所以得跟while循环一起配合使用。

3、无锁队列的head和tail之间不再是单纯的充满元素,在多线程环境下可能出现出队入队操作的竞争,所以每个slot的状态要分为4种:EMPTY(slot为空或者元素已出队,可以放入元素),TAKING(有线程正在取出队中的元素),FILLING(有线程正在塞入元素),FILLED(元素写入完毕,支持其他线程取出)。上述状态可以用enum表示,用GCC的自带函数__sync_bool_compare_and_swap()来改变。

4、使用2个信号量slot_availabledata_available来表示队内空槽和元素的数量,这样子就不需要判断head和tail指针是否会相遇。

5、其他编程技巧:使用sleep(0)让线程让出时间片防止一直循环。

C++参考代码,有错误欢迎提出:

#include <iostream>
#include <semaphore.h>
#include <atomic>
#include <unistd.h>
#include <thread>
#define CAS __sync_bool_compare_and_swap

struct Node {
    enum State {
        EMPTY = 0, TAKING, FILLING, FILLED,
    };
    int val;
    State state = EMPTY;
};

class Queue {
public:
    Queue(){}
    Queue(int size_) {
        size = size_;
        head = 0;
        tail = 0;
        queue = new Node[size];
        sem_init(&slot_available, 0, size);
        sem_init(&data_available, 0, 0);
    }
    bool enqueue(int val) {
        // 判断队列是否已满,空槽信号量是否有剩余
        if (sem_wait(&slot_available) != 0) {
            return false;
        }
        int curTail = tail.load();
        int newTail = (curTail + 1) % size;
        while (!tail.compare_exchange_weak(curTail, newTail)) {
            sleep(0);
        }
        while (!CAS(&queue[newTail].state, Node::EMPTY, Node::FILLING)) {
            sleep(0);
        }
        queue[newTail].val = val;
        while (!CAS(&queue[newTail].state, Node::FILLING, Node::FILLED)) {
            sleep(0);
        }
        // 释放数据信号量
        while (sem_post(&data_available) != 0) {
            sleep(0);
        }
        return true;
    }
    bool dequeue(int &val) {
        if (sem_wait(&data_available) != 0) {
            return false;
        }
        int curHead = head.load();
        int newHead = (curHead + 1) % size;
        while (!head.compare_exchange_weak(curHead, newHead)) {
            sleep(0);
        }
        while (!CAS(&queue[newHead].state, Node::FILLED, Node::TAKING)) {
            sleep(0);
        }
        val = queue[newHead].val;
        while (!CAS(&queue[newHead].state, Node::TAKING, Node::EMPTY)) {
            sleep(0);
        }
        // 释放空槽信号量
        while (sem_post(&slot_available) != 0) {
            sleep(0);
        }
        return true;
    }
private:
    Node *queue;
    std::atomic<int> head, tail;
    int size;
    sem_t slot_available;
    sem_t data_available;
};

int main() {
    Queue q(10);
    std::thread job1([&]{
        for (int i = 0; i < 10000; i++) {
            bool ret = q.enqueue(i);
            if (ret) {
                std::cout<<"enqueue "<<i<<std::endl;
            }
        }
    });
    std::thread job2([&]{
        int num = 0;
        for (int i = 0; i < 10000; i++) {
            bool ret = q.dequeue(num);
            if (ret) {
                std::cout<<"dequeue "<<num<<std::endl;
            }
        }
    });
    job1.join();
    job2.join();
    return 0;
}

 

posted @ 2023-11-27 07:36  宇宙之母蔡依林  阅读(242)  评论(0)    收藏  举报