如果我只有1个读取线程和1个写入线程,queue.empty()和queue.full()是否可以保证线程安全?

时间:2018-07-13 21:57:51

标签: c++ multithreading queue mutex shared-memory

通常,对于多线程访问的内存,我们将使用互斥锁将其锁定。 但是似乎下面的逻辑没有互斥锁是可以的,但是不确定在没有互斥锁的情况下下面的伪代码是否会发生竞争条件或潜在问题。

queue q;
int thread_read(){
    if(!q.empty())
       a = q.front();
       q.pop();
}

int thread_write(b){
    if(!q.full())
       p.push(b)
}

2 个答案:

答案 0 :(得分:0)

除非队列使用互斥锁(或至少类似类似的东西,例如内存栅栏,原子变量等),是的,这很容易出现问题。

例如,让我们假设一个queue定义为使用一些(单个)变量来容纳队列中当前有多少可用空间。因此,您的pushpop的代码类似于:

push(item) {
    --space_avail;
    // ...
}

pop() {
    ++space_avail;
}

这样编写时,问题可能并不明显,所以让我们看一下我们可能希望看到的代码类型:

// --space_avail
mov r0, space_avail
dec r0
mov space_avail, r0

// ++ space_avail
mov r0, space_avail
inc r0
mov space_avail, r0

现在问题可能更明显了。如果我们的生产和消费者线程像这样重叠:

T0: mov r0, space avail
T1: mov r0, space_avail
T1: dec r0
T1: mov space_avail, r0
T0: inc r0
T0: mov space_avail, r0

如果我们进行推和弹出操作,我们期望space_avail最终会获得与开始时相同的值,但是在这种情况下,它不会这样做;它比开始时大一个。您可能还可以很容易地看到,在dec / store序列和inc / store序列之间进行交换会在相反的方向发生变化,因此space_avail会比开始时要小。

显然,这两种情况都会导致无法正常工作的队列。

答案 1 :(得分:0)

首先,queue :: full不是std的一部分,因此,如果您使用的是自定义队列,我们​​将无法在不了解内部实现的情况下回答该问题。

第二,在您的伪代码中,如果队列中有例如1个项目,并且假设大小为8,则将同时满足两个条件并且都将输入其逻辑。尽管您对方法进行了命名,但是这两个函数都执行写操作,因为queue :: pop和queue :: push都更改了队列对象的状态。

如果两个并发线程修改了一个对象,则必须始终使用互斥体来确保数据的一致性。