2023阿里云面试题——实现一个生产者消费者模型,使用无锁循环数组/队列(C++)
这个面试题的重点:
1、循环数组:元素在放入数组时需要模数组的长度来确认放入位置,由此判断队列是空是满的条件就不一样了。
数组有指针head和tail分别指向队头和队尾,head指向第一个元素,队尾指向最后一个元素。
一开始数组为空,head = tail,就可以判定消费者无法取得元素;
如果数组满了,那么(tail + 1) % array.length = head,此时生产者无法放入元素。
2、无锁:那就不能使用std::mutex、std::unique_lock之类的结构,只能用std::atomic,但是std::atomic只有针对基本数据类型才有自加自减的原子操作,所以得用原子交换来实现head、tail指针的替换,比如compare_exchange_strong()和。weak相比strong的话可能会有两值比较相等但依旧返回false的情况,所以得跟while循环一起配合使用。compare_exchange_weak()
3、无锁队列的head和tail之间不再是单纯的充满元素,在多线程环境下可能出现出队入队操作的竞争,所以每个slot的状态要分为4种:EMPTY(slot为空或者元素已出队,可以放入元素),TAKING(有线程正在取出队中的元素),FILLING(有线程正在塞入元素),FILLED(元素写入完毕,支持其他线程取出)。上述状态可以用enum表示,用GCC的自带函数__sync_bool_compare_and_swap()来改变。
4、使用2个信号量slot_available和data_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; }

浙公网安备 33010602011771号