【问题标题】:Safe removal of a node in a concurrent linked list安全删除并发链表中的节点
【发布时间】:2018-08-09 09:10:52
【问题描述】:

我目前正在阅读 APUE 这本书。当我阅读 pthread reader/writer-lock 的章节时,我有一个关于它使用 reader/writer-lock 实现并发队列的问题。

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 * 是动态分配的数据结构,这可能会导致双重释放错误。有什么建议吗?

【问题讨论】:

  • “两个线程可以在 pthread_rwlock_wrlock 行之前停止”是什么意思?为什么线程会停止?
  • 我的意思是这些线程可以被调度,以便两个线程都运行到那个步骤。
  • 您必须查看pthread_rwlock_wrlock 的源代码,但我的猜测是有一个 if 语句可以检查锁是否已设置或可用。这应该相当于一条机器指令,这意味着这是一个线程获得锁的地方,而另一个线程将不得不等到锁被释放。
  • 发布的代码可能无法编译。它在else if (jp == qp->q_tail) 之前缺少一个右括号。

标签: c concurrency linked-list readwritelock


【解决方案1】:

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

代码需要信息让它知道该节点已被删除。

查看添加的 cmets,以及修复竞态条件的代码:

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 或deleted。不能允许多个线程这样做。

【讨论】:

  • 因此,您在这里尝试解决的问题是,如果有两个线程访问该部分代码,而另一个线程在后台不断调用 delete?
  • 也就是说,需要某种 C++ 的 shared_ptr 或垃圾收集器才能安全地移除一个动态分配的节点?
  • @AlexandreMercierAubin 我没有解决任何特殊情况。我正在删除一个竞争条件,该条件允许一个节点被删除多个,可能会破坏处于未知状态的队列。
  • @manifold 也就是说,需要某种 C++ 的 shared_ptr 或垃圾收集器才能安全地删除动态分配的节点? 不。虽然这样的结构使多线程编码更容易,但它们'没有必要。
  • 与该结构体相关的其他函数使用相同的变量和锁定过程进行锁定。除非用户愿意尝试通过不使用它附带的函数来弄乱结构,否则不应该存在竞争条件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-08
相关资源
最近更新 更多