安全删除并发链接列表中的节点

时间:2018-08-09 09:10:52

标签: c concurrency linked-list readwritelock

我目前正在阅读《 APUE》一书。当我阅读有关pthread读取器/写入器锁的章节时,我对使用读取器/写入器锁实现并发队列的问题有所疑问。

struct queue {
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};

/*
* Remove the given job from a queue.
*/
void
job_remove(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&qp->q_lock);
    if (jp == qp->q_head) {
        qp->q_head = jp->j_next;
        if (qp->q_tail == jp)
            qp->q_tail = NULL;
        else
            jp->j_next->j_prev = jp->j_prev;
    } else if (jp == qp->q_tail) {
        qp->q_tail = jp->j_prev;
        jp->j_prev->j_next = jp->j_next;
    } else {
        jp->j_prev->j_next = jp->j_next;
        jp->j_next->j_prev = jp->j_prev;
    }
    pthread_rwlock_unlock(&qp->q_lock);
}

我的问题是,此实现如何确保struct job仅从链接列表中删除一次。据我了解,可以安排两个线程,使其恰好位于行pthread_rwlock_wrlock之前。然后struct job *jp可能会被释放两次。如果struct job *是动态分配的数据结构,则可能导致双重错误。有什么建议吗?

1 个答案:

答案 0 :(得分:0)

您的代码中包含竞争条件。在两个线程之间的队列上可能会发生其他更改,从而获得队列上的写锁,然后尝试删除同一节点。可以删除其他节点,可以添加其他节点。因此,如果线程A删除一个节点,发生更改,然后线程B再次尝试删除同一节点,则您的队列可能会损坏。

代码需要信息以使其知道该节点已被删除。

请参阅添加的注释,以及解决竞态条件的代码:

struct queue {
    struct job *q_head;
    struct job *q_tail;
    pthread_rwlock_t q_lock;
};

/*
* Remove the given job from a queue.
*/
void
job_remove(struct queue *qp, struct job *jp)
{
    // we assume here that jp is actually in the queue
    pthread_rwlock_wrlock(&qp->q_lock);

    // at this point, jp may no longer be in the queue,
    // and in fact, the queue may be completely different.
    // thus, any modification to the queue based on the
    // assumption that jp is still part of the queue
    // can lead to corruption of the queue

    // so check if jp has been removed - we'll later set
    // both j_next and j_prev to NULL after jp is
    // removed from the queue - and if they're both
    // NULL here that means another thread already
    // removed jp from the queue
    if ( !(jp->j_next) && !(jp->j_prev) ) {
        // empty statement - jp is already removed
        ;
    }
    else if (jp == qp->q_head) {
        qp->q_head = jp->j_next;
        if (qp->q_tail == jp)
            qp->q_tail = NULL;
        else
            jp->j_next->j_prev = jp->j_prev;
    } // and this brace was missing in your posted code...
    else if (jp == qp->q_tail) {
        qp->q_tail = jp->j_prev;
        jp->j_prev->j_next = jp->j_next;
    } else {
        jp->j_prev->j_next = jp->j_next;
        jp->j_next->j_prev = jp->j_prev;
    }

    // make sure data in jp no longer refers to
    // the queue - this will also tell any other
    // thread that accesses jp that it's already
    // been removed
    jp->j_next = NULL;
    jp->j_prev = NULL;

    pthread_rwlock_unlock(&qp->q_lock);
}

您还需要检查jp如何获得free() d或delete d。不允许有多个线程这样做。