C++ std::deque和多线程访问

C++ std::deque和多线程访问,c++,multithreading,queue,C++,Multithreading,Queue,请帮我理解这个。我是多线程编程和排队技术的新手。 我目前有两个线程在同一个deque上工作,如下所示:std::dequeinputQueue。视频帧是我的数据结构。 每个线程都有指向该共享资源的指针:std::deque*p_inputQueue。 我还使用:std::mutex mtxlock制作了一个全局关键部分; 也就是说,第一个线程的任务是不断地将数据推回deque: 对于第二个线程,它有一个循环来访问队列的第一个元素,执行一些不改变值的计算,然后弹出前端。所以我想知道更好的方法是什么

请帮我理解这个。我是多线程编程和排队技术的新手。 我目前有两个线程在同一个deque上工作,如下所示:std::dequeinputQueue。视频帧是我的数据结构。 每个线程都有指向该共享资源的指针:std::deque*p_inputQueue。 我还使用:std::mutex mtxlock制作了一个全局关键部分; 也就是说,第一个线程的任务是不断地将数据推回deque:

对于第二个线程,它有一个循环来访问队列的第一个元素,执行一些不改变值的计算,然后弹出前端。所以我想知道更好的方法是什么? 这个:

或使用迭代器:

我还想知道我是否需要像那样锁定整个队列

编辑:
感谢您迄今为止的所有回答和评论。我只是想把事情说清楚一点。将元素从队列复制/移动到对象并不是一个便宜的过程,所以我考虑改用指针/迭代器。抱歉,我没有早点编辑。

我怀疑你们两种方法之间有什么显著的区别

如果您的程序逻辑允许,我建议您在开始处理之前将元素从队列中移除并解锁队列。这样就不会阻塞另一个线程太长时间。显然,这只有在处理逻辑除了队列访问本身之外不存在其他潜在竞争条件的情况下才有可能:

mtxlock.lock();

//make a copy
VideoFrame frame = p_inputQueue->front();    
p_inputQueue->pop_front();
mtxlock.unlock();

//A very long computing process using object frame, after unlock

如果VIEFFRAMEY对象太重或不实用,请考虑保持一个指针队列,而不是STD::DEQE,甚至STD::DEQU.< /P> < P>我怀疑你们两种方法之间有什么显著的差异。 如果您的程序逻辑允许,我建议您在开始处理之前将元素从队列中移除并解锁队列。这样就不会阻塞另一个线程太长时间。显然,这只有在处理逻辑除了队列访问本身之外不存在其他潜在竞争条件的情况下才有可能:

mtxlock.lock();

//make a copy
VideoFrame frame = p_inputQueue->front();    
p_inputQueue->pop_front();
mtxlock.unlock();

//A very long computing process using object frame, after unlock

如果VIEW文件对象太重或不太实用,请考虑保持一个指针队列,而不是STD::DEQE,甚至STD::DEQU.< /P> < P>假设您实际上是用迭代的方式来迭代一个集合,作为使用者的迭代器方法将锁定队列,直到队列中的每个元素都已处理完毕。这将防止生产者在队列中写入新元素

在单一生产者、单一消费者的情况下,这可能是问题,也可能不是问题,这取决于生产和消费的相对速度

我的意思是,队列的使用通常用于处理生产和消费解耦的情况,否则您可以直接从生产者调用消费代码

例如,生产端可能会出现周期性的高活动突发,这将导致许多项目进入队列,因为消费端无法跟上

在这种情况下,队列被用作等待点,以便消费方可以在低活动生产时间赶上

在使用队列上的所有元素期间锁定队列基本上会使生产者暂时停止,这意味着缓冲是无用的

如果你转向多消费者模式,它也会引起真正的问题

多个使用者的全部目的是平衡多个使用者之间的负载,以提高总体吞吐量。如果每个消费者只是抓住队列并处理队列上的所有项目,就不会发生这种情况

在这种情况下的任何给定时间点,一次只有一个消费者处于活动状态

a如果您只是使用迭代器获取第一个元素,那么您的锁可能具有更高的分辨率


但是,这倒是第一次使用迭代器的失败,所以我认为不太可能。

< P>假设你实际上是用迭代的方式来迭代一个集合,你的迭代器方法作为一个消费者将锁定队列,直到队列中的每个元素都被处理了。这将防止生产者在队列中写入新元素

在单一生产者、单一消费者的情况下,这可能是问题,也可能不是问题,这取决于生产和消费的相对速度

我的意思是,队列的使用通常用于处理生产和消费解耦的情况,否则您可以直接从生产者调用消费代码

例如,生产端可能会出现周期性的高活动突发,这将导致许多项目进入队列,因为消费端无法跟上

在这种情况下,队列 用作停工待检点,以便消费方能够在低活动生产时间赶上

在使用队列上的所有元素期间锁定队列基本上会使生产者暂时停止,这意味着缓冲是无用的

如果你转向多消费者模式,它也会引起真正的问题

多个使用者的全部目的是平衡多个使用者之间的负载,以提高总体吞吐量。如果每个消费者只是抓住队列并处理队列上的所有项目,就不会发生这种情况

在这种情况下的任何给定时间点,一次只有一个消费者处于活动状态

a如果您只是使用迭代器获取第一个元素,那么您的锁可能具有更高的分辨率


但是,这倒是第一次使用迭代器的失败,所以我认为不太可能。

在处理过程中,你绝对不想持有锁。您有几个选择:

如果复制对象的成本很低,请复制它,调用pop_front,释放锁,然后处理复制

如果您使用的是C++11,而且移动对象的成本很低,请移动它,调用pop_front,释放锁,然后处理新对象

使用deque。从前面复制/移动共享\u ptr,调用pop\u front,释放锁,然后使用共享\u ptr的副本处理对象

使用唯一的\u ptr而不是共享的\u ptr。获取锁,将unique_ptr从前面移出,调用pop_front,释放锁,然后使用移动到的unique_ptr处理对象

使用deque并确保在使用完视频帧后将其删除


在处理过程中,您肯定不想持有锁。您有几个选择:

如果复制对象的成本很低,请复制它,调用pop_front,释放锁,然后处理复制

如果您使用的是C++11,而且移动对象的成本很低,请移动它,调用pop_front,释放锁,然后处理新对象

使用deque。从前面复制/移动共享\u ptr,调用pop\u front,释放锁,然后使用共享\u ptr的副本处理对象

使用唯一的\u ptr而不是共享的\u ptr。获取锁,将unique_ptr从前面移出,调用pop_front,释放锁,然后使用移动到的unique_ptr处理对象

使用deque并确保在使用完视频帧后将其删除


在我看来,这两种方法都不太理想。我倾向于使用简单指针而不是迭代器。但是,在deque中以值存储对象时,使用迭代器/指针指向正在处理的对象是有问题的

如果按值将帧对象存储在deque中,这是否意味着复制成本较低?他们能被移动吗?如果是这样的话,我很想在处理之前将它们复制/移出deque,这样可以让您更快地释放锁

或者,您可以在deque中存储指向帧的指针,而不是值

无论哪种方式,您发布的方法的问题是,如果在锁定互斥锁之后发生异常,但在解锁互斥锁之前,deque将保持锁定状态,那么它不是异常安全的。它还为延长的处理周期锁定deque:

// not exception safe
mtxlock.lock();

// Taking the address means locking the whole queue
// until you have finished with the one element
VideoFrame* pFrame = &p_inputQueue->front();

//A very long computing process using pointer *pFrame
p_inputQueue->pop_front();

// not exception safe
mtxlock.unlock();
我倾向于从deque复制/移动对象,并使用std::lock\u guard确保异常安全。如果出现异常,它将自动释放锁

// ready to copy/move the object by value
VideoFrame frame;

{ // start a new block for the lock

    // get a local automatic lock
    std::lock_guard<std::mutex> lock(mtxlock); 

    // copy/move the object out of the queue
    // so you can release the lock immediately
    frame = std::move(p_inputQueue->front());
    p_inputQueue->pop_front();

} // lock is released here

// Now it doesn't matter how long it takes
// to process the object

在我看来,这两种方法都不太理想。我倾向于使用简单指针而不是迭代器。但是,在deque中以值存储对象时,使用迭代器/指针指向正在处理的对象是有问题的

如果按值将帧对象存储在deque中,这是否意味着复制成本较低?他们能被移动吗?如果是这样的话,我很想在处理之前将它们复制/移出deque,这样可以让您更快地释放锁

或者,您可以在deque中存储指向帧的指针,而不是值

无论哪种方式,您发布的方法的问题是,如果在锁定互斥锁之后发生异常,但在解锁互斥锁之前,deque将保持锁定状态,那么它不是异常安全的。它还为延长的处理周期锁定deque:

// not exception safe
mtxlock.lock();

// Taking the address means locking the whole queue
// until you have finished with the one element
VideoFrame* pFrame = &p_inputQueue->front();

//A very long computing process using pointer *pFrame
p_inputQueue->pop_front();

// not exception safe
mtxlock.unlock();
我倾向于从deque复制/移动对象,并使用std::lock\u guard确保异常安全。如果出现异常,它将自动释放锁

// ready to copy/move the object by value
VideoFrame frame;

{ // start a new block for the lock

    // get a local automatic lock
    std::lock_guard<std::mutex> lock(mtxlock); 

    // copy/move the object out of the queue
    // so you can release the lock immediately
    frame = std::move(p_inputQueue->front());
    p_inputQueue->pop_front();

} // lock is released here

// Now it doesn't matter how long it takes
// to process the object

我要指出,@Trung Nguyen的两种方法都是错误的,因为他使用指针/迭代器而不是复制,而复制在pop_front之后无效。虽然@Smeeheey的答案正确,但要复制。@我的-他在完成指针/迭代器后才调用pop_front谢谢你,是的,在我的情况下,将元素复制到对象是不切实际的方法。这就是为什么我尝试使用指针或迭代器。目前,锁定也是我关注的问题之一。我真的很想得到使用多线程的好处,b

但是处理时间花费了大量的时间,甚至我的程序也像单个线程一样工作,因为第一个线程总是等待第二个线程完成它的工作。在这种情况下,我强烈建议将您的设计改为使用指针队列。这将解决整个处理过程中的锁定问题。我要指出,@Trung Nguyen的两种方法都是错误的,因为他使用指针/迭代器而不是复制,而复制在pop_front之后无效。虽然@Smeeheey的答案正确,但要复制。@我的-他在完成指针/迭代器后才调用pop_front谢谢你,是的,在我的情况下,将元素复制到对象是不切实际的方法。这就是为什么我尝试使用指针或迭代器。目前,锁定也是我关注的问题之一。我真的很想利用使用多线程的优势,但处理时间比我的程序像单个线程一样花费了大量时间,因为第一个线程总是等待第二个线程完成它的工作。在这种情况下,我强烈建议将您的设计改为使用指针队列。这将解决整个处理过程中的锁定问题。使用迭代器方法作为使用者将锁定队列,直到队列中的每个元素都已处理完毕为止—OP的代码片段中没有任何内容表明会出现这种情况。他锁定、处理单个元素,然后解锁。在哪里可以看到循环在再次解锁之前消耗了所有元素?@Smeeheey,这是因为OP实际上使用了迭代器,而不管发布的代码是什么。使用迭代器只获取队列的第一个元素没有什么意义,迭代器通常用于。。。好迭代集合:-我将用这个假设来澄清我的答案。他特别比较了使用指向前元素的指针和使用指向前元素的迭代器。超出front元素没有任何意义。@Smeeheey,在这种情况下,使用迭代器完全是浪费时间。使用迭代器方法作为使用者将锁定队列,直到队列中的每个元素都被处理完毕为止。OP的代码段中没有任何内容表明会出现这种情况。他锁定、处理单个元素,然后解锁。在哪里可以看到循环在再次解锁之前消耗了所有元素?@Smeeheey,这是因为OP实际上使用了迭代器,而不管发布的代码是什么。使用迭代器只获取队列的第一个元素没有什么意义,迭代器通常用于。。。好迭代集合:-我将用这个假设来澄清我的答案。他特别比较了使用指向前元素的指针和使用指向前元素的迭代器。超出front元素没有任何意义。@Smeeheey,在这种情况下,使用迭代器完全是浪费时间。你的意思是如果我使用第5个选项,我真的不需要为我的队列锁定或调用pop_front,只需删除?@Trunguyen你需要一个锁来保护对deque的并发访问,您确实需要调用pop_front来删除deque中的指针。您必须避免一个线程访问deque,而另一个线程可能正在修改它。所有这些方法的目标都是确保锁的持有时间不会超过需要的时间;然后调用p_inputQueue->pop_front;所以在调用delete pFrame之前,pFrame仍然是一个有效的指针@是的,pop_front只去掉了deque上的指针。您不再需要它的值,因为您有它的副本。除去常规指针对它指向的对象没有任何影响,除非它指向自身!。那么你的意思是如果我使用第五个选项,我真的不需要为我的队列锁定或调用pop_front,只需删除?@trunnguyen您确实需要一个锁来保护对deque的并发访问,并且您确实需要调用pop_front来删除deque中的指针。您必须避免一个线程访问deque,而另一个线程可能正在修改它。所有这些方法的目标都是确保锁的持有时间不会超过需要的时间;然后调用p_inputQueue->pop_front;所以在调用delete pFrame之前,pFrame仍然是一个有效的指针@是的,pop_front只去掉了deque上的指针。您不再需要它的值,因为您有它的副本。除去常规指针对它指向的对象没有任何影响,除非它指向自身!。
// store pointers in the queue
VideoFrame* pFrame;

{ // start a new block for the lock

    // get a local automatic lock
    std::lock_guard<std::mutex> lock(mtxlock);

    // copy the pointer out of the queue
    // so you can release the lock immediately
    pFrame = p_inputQueue->front();
    p_inputQueue->pop_front();

} // lock is released here

// Now it doesn't matter how long it takes
// to process the object
// store unique pointers in the queue
std::deque<std::unique_ptr<VideoFrame>>* p_inputQueue;

// ...

std::unique_ptr<VideoFrame> pFrame;

{ // start a new block for the lock

    // get a local automatic lock
    std::lock_guard<std::mutex> lock(mtxlock);

    // move the unique pointer out of the queue
    // so you can release the lock immediately
    pFrame = std::move(p_inputQueue->front());
    p_inputQueue->pop_front();

} // lock is released here

// Now it doesn't matter how long it takes
// to process the object