微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

是否可以通过同时推送和弹出来创建线程安全队列?

如何解决是否可以通过同时推送和弹出来创建线程安全队列?

这更多是理论上的问题,而不是有用的问题,但是我想不出一种解决方法。明确地说,目标是创建一个队列,该队列允许多个线程同时推送和弹出而不会互相阻塞。

目标的一个示例是: 线程A推送两次。线程A和线程B调用push,线程C和线程D调用pop。线程A获得队列位置2,线程B获得队列位置3,线程C获得队列位置0,线程D获得队列位置1。所有线程都可以同时读取/写入各自的位置。

这些push和pop函数可以执行此操作,但是它们不是线程安全的:

#include <pthreads.h>
#include <semaphore.h>

...

void push(void* item) {
    sem_wait(push_avaliability);
    int slot = atomic_fetch_add(tail,1) % queue_size;
    pthread_mutex_lock(access_queue[slot]);
    queue[slot] = item;
    pthread_mutex_unlock(access_queue[slot]);
    sem_post(pop_avaliability);
}

void* pop() {
    sem_wait(pop_avaliability);
    int slot = atomic_fetch_add(head,1) % queue_size;
    pthread_mutex_lock(access_queue[slot]);
    void* item = queue[slot];
    pthread_mutex_unlock(access_queue[slot]);
    sem_post(push_avaliability);
}

headtail都初始化为0,push_avaliability初始化为队列的大小,pop_avaliability初始化为0。这是仅有的两个修改这些变量的函数。 (我知道头/尾存在溢出的可能性,并且将指针存储在数组中会使队列的线程安全性变得不重要,但是这些问题对这个问题并不重要。)

问题的一个示例是: 假设线程A和线程B调用push,线程C调用pop。线程A获得插槽0,但没有锁定它,然后线程B获得插槽1并写入它,并声明有写入的空间。线程C唤醒并获得插槽0,并尝试从中读取,但是线程A尚未写入该插槽。

我可以通过增加互斥量的头/尾和写入/读取来解决此问题,以防止任何其他线程访问整个队列,但是我想知道是否有可能以允许多个线程访问的方式进行操作同时要写入队列和从队列读取线程。

解决方法

这是我正在使用的解决方案:

void push(void* item) {
    sem_wait(push_avaliability);
    int slot = atomic_fetch_add(tail,1) % queue_size;
    while(slot_full[slot] != false);
    queue[slot] = item;
    slot_full[slot] = true;
    sem_post(pop_avaliability);
}

void* pop() {
    sem_wait(pop_avaliability);
    int slot = atomic_fetch_add(head,1) % queue_size;
    while(slot_full[slot] != true);
    void* item = queue[slot];
    slot_full[slot] = false;
    sem_post(push_avaliability);
    return item;
}

({slot_full[i]初始化为false)

while循环不会花费大量时间,因为信号量主要用于等待插槽打开。 (在测试中,while循环条件永远不会成立。)如果复制操作很昂贵,那么使用条件变量将是可行的方法,但是我认为平均而言,这种方法可能比此解决方案要昂贵。

This paper是无锁队列的有用参考,以及mpoeter的评论。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。