锁定std :: list的有效方法

时间:2014-07-15 08:02:08

标签: c++ multithreading list

我知道std::list不是线程安全的。在我的应用程序线程中,不断添加元素到全局列表。另一个线程从列表中获取元素并逐个处理它们。但是,我不希望处理线程在处理过程中始终锁定列表。

因此处理线程锁定列表,获取元素,解锁列表并处理元素。在处理过程中,其他线程不断向列表中添加元素。处理完成后,处理线程再次锁定列表删除已处理的元素并将其解锁。

以下是伪代码:

std::list<int> mylist ; /* Global list of integers */

void add_thread(int element) /* Threads adding element to the list */
{
   write_lock();
   mylist.push_back(element);
   write_unlock();

   return;
}

void list_processing_thread() /* Processes elements from the list */
{
    for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it)
    {
        read_lock();
        int element = *it;
        read_unlock();

        process_element(element);

        write_lock();
        mylist.remove(element);
        write_unlock();
    }

    return;
}

这是正确的方法(以有效的方式处理列表元素)吗?会不会有麻烦?

5 个答案:

答案 0 :(得分:1)

  

因此处理线程锁定列表,获取元素,解锁列表并处理元素。在处理过程中,其他线程不断向列表中添加元素。处理完成后,处理线程再次锁定列表删除已处理的元素并将其解锁。

生产者线程相互竞争,并与消费者线程争用以访问列表。

您可以通过为每个生产者提供自己的队列(std :: list + std :: mutex或spinlock)来消除生产者之间的争用。这样:

  1. 生产者将项目发布到自己的队列中。创建一个元素的临时std::list,然后锁定互斥锁,将该元素拼接到队列中,解锁互斥锁。
  2. 当使用者准备就绪时,它会逐个锁定这些生成器队列,并将这些元素拼接到自己的队列中。拼接std::list是O(1)操作。
  3. 如果需要排序,则每个元素都应该有一个时间戳,以便使用者可以按时间对其统一队列中的所有元素进行排序。
  4. 上述方法也会使你的关键部分变得非常短,因为在互斥锁被锁定时所做的只是std::list拼接,这只是一些指针修改。

    或者,只需使用英特尔®线程构建模块中的concurrent_bounded_queue类。

答案 1 :(得分:0)

不,这不安全。是的,它会造成麻烦。 详细说明: 应该只有一个锁是写锁。读者不需要互相辩护。 在访问开始和结束迭代器之前,您需要在处理线程中获取锁,因为它们可能被写入线程损坏。获得锁定后,复制列表并释放锁定。完成处理后,获取锁定,然后编辑原始列表。 祝你好运

答案 2 :(得分:0)

在C ++ 11中,这可能是您的问题的解决方案,但您可以轻松地将其移植到C ++ - 03 + boost或您拥有的任何线程库:

std::deque<int> queue;
std::mutex mtx;
std::condition_variable ready;

void producer()
{
  while (true)
  {
    int element = produce();

    std::unique_lock<std::mutex> lock(mtx);
    queue.push_back(element);
    lock.unlock(); 
    ready.notify_one();
  }
}

void consumer()
{
  while (true)
  {
    std::unique_lock<std::mutex> lock(mtx);
    ready.wait(lock, [](){ ! queue.empty() });
    int element = queue.front();
    queue.pop_front();
    lock.unlock();

    consume(element);
  }
}

答案 3 :(得分:0)

对于不同级别的性能,您可以做很多事情,但有两个非常简单:

  • 在处理线程中使用std::list::swap将队列与空队列进行交换,这样如果锁争用正在减慢速度,至少你会得到所有积压的项目一击,最小化伤害

  • 使用单作者/多读者锁定(例如posix_rwlock_rdlock等)

答案 4 :(得分:-1)

首先,方法很好,你应该锁定一小段时间,获取当前项目,解锁并处理项目,同时列表被解锁并可供其他线程使用。

但是,我建议您在列表未锁定时不依赖列表迭代器。迭代器是偷偷摸摸的,当添加/删除项目时,许多数据结构可能会破坏迭代器。

我建议你使用以下方法:

while(list.empty() == false)
{
  lock();
  int element = list.front();
  list.pop_front();
  unlock();

  process(element);
}

最好!