繁体   English   中英

如何在迭代列表时最小化互斥锁,同时能够从另一个线程添加元素?

[英]How to minimize mutex locks when iterating a list while being able to add elements from another thread?

在工作线程中,我遍历需要更新的项目列表。 在主线程中,用户经常调用一个函数以将元素添加到该列表中。 我正在使用互斥锁来防止列表在迭代时被修改。 通常,伪代码如下所示:

// called by the client to add an element.
void AddElementToList(element)
{
    // lock mutex
    // add element to the list
    // unlock mutex
}

// this function is run by the worker thread
void WorkerThreadUpdate()
{
    // lock mutex
    // for each element in the list
    //     update the element
    //     mark the element for deletion if finished
    // for every finished element
    //     remove element from list
    // unlock mutex
}

问题在更新元素时​​出现。 元素的更新功能可能会花费很长时间,在某些情况下可能需要大约一秒钟。 发生这种情况时,用户的下一个对AddElementToList块的调用将导致其应用程序冻结,直到所有元素都已更新。 通常经常调用AddElementToList函数,这是一个非常明显的问题。

所以我正在寻找更好的方法。 我想在保护列表的同时更新元素,但要使用户的主线程保持响应状态。 我敢肯定,这几乎是多线程101,但是我无法提出正确的术语来找到我要寻找的示例。

我正在考虑使用两个列表的解决方案,即“请求”列表和“工作”列表。

用户调用AddElementToList,将元素添加到请求列表中。 在工作线程中,迭代工作列表后,它将遍历请求列表,并将其元素添加到下一帧的工作列表中。 它仅在实际修改列表时锁定。

// called by the client to add an element.
void AddElementToList(element)
{
    // lock mutex
    // add element to the request list
    // unlock mutex
}

// this function is run by the worker thread
void WorkerThreadUpdate()
{
    // for each element in the work list
    //     update the element
    //     mark the element for deletion if finished

    // for every finished element
    //     lock mutex
    //     remove element from work list
    //     unlock mutex

    // lock mutex
    // for every element in the request list
    //     add element to the work list
    // clear the request list
    // unlock mutex
}

我认为这应该可行,但是我不确定。 这是可以接受的方式吗? 有更好的方法来解决这个问题吗?

您计划添加的队列应该可以工作。 是否可接受取决于等待队列添加直到线程执行下一次“更新”传递是否可接受。

在更新过程中是否甚至需要锁定列表? 还有什么访问此列表的时间? 您可以锁定列表,创建参考的副本矢量/列表,解锁列表,然后对每个副本ref一对一运行更新吗?

让我先问这些:

  • 实施中的清单实际上是什么? 动态数组? 链表? 一些哈希表? -如果是列表,则对一个元素的操作应仅影响下一个,前一个元素以及可能的头和尾。
  • 如何将元素添加到列表中? 总是在结尾? 还是可以在中间某处插入?

假设:

  • 它是一个单向或双向链接列表,带有指向其头部和尾部的其他变量。
  • 您只能在最后添加元素。

如果是这种情况,我建议您这样做类似于:

void AddElementToList(element) {
    mutex_lock("push");
    list.push_back(element);
    mutex_release("push");
}

void WorkerThreadUpdate() {
    AddElementToList(specialElement);
    /*
      at this point we are guaranteed that other threads,
      which can only add to the list,
      will not affect us, because we have a "guard" between us and them
    */
    iterator del=NULL;
    iterator i=list.begin();
    for(; *i!=specialElement; ++i) {
         if (del) {
             del->remove();
             del=NULL;
         }
         i->update();
         if (some_condition)
             del=i;
    }
    //*i == specialElement now
    /*
      We are now operating on the guard, so we have to be careful.
      "specialElement" could still be the last element in the list.
    */
    mutex_lock("push");
    i->remove();
    mutex_release("push");
}

我不确定标准STL是否是线程安全的,是否可以使用它们的列表实现。 阅读他们的规格。 如果不是,请实施您自己的列表容器。

是否有可能另一个线程尝试访问您正在更新的元素。 如果没有,您可以在开始更新之前释放列表上的锁定,并在想要继续进行迭代(或删除对象)时重新获取它。 类似于以下内容:

void
WorkerThreadUpdate()
{
    // lock mutex
    ListType::iterator current = list.begin();
    while ( current != list.end() ) {
        ListType::value_type& obj = *current;
        //  unlock mutex
        //  do modifications...
        //  lock mutex
        if ( needsDeleting ) {
            current = list.erase( current );
        } else {
            ++ current;
        }
    }
    //  unlock mutex
}

重要的是,在实际访问迭代器时(至少使用任何标准容器),您必须持有锁。

当然,为了以防万一,您需要使用某种范围锁。 (尽管可能没有必要;我认为锁定区域中的所有代码都应该没有抛出。)我没有尝试过,但是如果您拥有C ++ 11,我认为您可以使用std::unique_lock

恕我直言,虽然这可行,但并不特别优雅。 我可能会将列表完全保留在工作程序端,并使用消息队列传递要插入的对象,而工作线程会不时从消息队列中读取消息,以便插入对象。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM