请帮我理解这个。我是多线程编程和排队技术的新手。
我目前有2个主题正在处理相同 deque
,如下所示:std::deque< VideoFrame > inputQueue
。 (VideoFrame是我的数据结构)。
并且每个线程都有指向该共享资源的指针:std::deque< VideoFrame > *p_inputQueue
。
我还使用以下方法创建了一个全局关键部分:std::mutex mtxlock;
假设,第一个主题的工作是将push_back
数据连续deque
:
mtxlock.lock();
p_inputQueue->push_back(frame);
mtxlock.unlock();
对于第二个线程,它有一个循环来访问队列的第一个元素,做一些计算(不改变值),然后pop_front
。所以我想知道什么是更好的方法?
这一个:
mtxlock.lock();
VideoFrame *pFrame = &p_inputQueue->front();
//A very long computing process using pointer *pFrame
p_inputQueue->pop_front();
mtxlock.unlock();
或使用迭代器:
mtxlock.lock();
std::deque< VideoFrame >::iterator it = p_inputQueue->begin();
//A very long computing process using *it
p_inputQueue->pop_front();
mtxlock.unlock();
我也想知道我是否需要像这样锁定整个队列?
修改 感谢您到目前为止的所有答案和评论。我只是想把事情弄清楚一点。将元素从队列复制/移动到对象并不是一个廉价的过程,所以我考虑使用指针/迭代器。很抱歉,如果没有及早编辑。
答案 0 :(得分:4)
我怀疑你的两种方法之间有什么重大差异。
我建议的一件事(如果您的程序逻辑允许),就是在开始处理之前将元素从队列中取出并解锁队列。这样,您就不会在相当长的时间内阻止其他线程。显然,除了队列访问本身之外,处理逻辑不会引入其他潜在的竞争条件,这才有可能:
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
如果VideoFrame
对象太重或无法复制,请考虑保留指向它们的指针队列(即std::deque<VideoFrame*>
甚至std::deque<std::unique_ptr<VideoFrame>>
)。
答案 1 :(得分:4)
你绝对不想在处理过程中持有锁。你有几个选择:
如果要复制的对象很便宜,请复制它,调用pop_front
,释放锁定,然后处理副本。
如果您正在使用C ++ 11并且移动对象便宜,请移动它,调用pop_front
,释放锁定,然后处理新对象。
使用deque<shared_ptr<VideoFrame>>
。复制/移动前面的shared_ptr
,调用pop_front
,释放锁定,然后使用shared_ptr
的副本处理对象。
使用unique_ptr
代替shared_ptr
。获取锁定,移开前面的unique_ptr
,调用pop_front
,释放锁定,然后使用移动到的unique_ptr
处理对象。
使用deque<VideoFrame*>
并确保delete
完成后VideoFrame
。
答案 2 :(得分:1)
这两种方法对我来说都不太理想。我倾向于更喜欢迭代器上的简单指针。但是,在 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_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 中存储指针:
// 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
如果您决定在 deque 中存储指针,那么我建议您使用智能指针,例如std::unique_ptr
。这相当于您通过 value 存储框架:
// 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
答案 3 :(得分:0)
假设您实际上正在按照预期的方式使用迭代器(迭代集合),作为使用者的迭代器方法将锁定队列,直到队列中的每个元素为止已处理(a)。这将阻止生产者在发生这种情况时将新元素写入队列。
根据生产和消费的相对速度,这可能是也可能不是单一生产者,单一消费者情景中的问题。
通过这种方式,我的意思是使用队列通常是为了处理生产和消费分离的情况,否则你可以直接从生产者那里调用消费者代码。
例如,您可能会在生产方面定期进行高活动突发,这将导致许多项目进入队列,因为消费方无法跟上。
在这种情况下,队列被用作保留点,因此消费方可以在低活动生产时间内赶上。
在消耗队列中的所有元素期间锁定队列将基本上停止生产者那段时间,这意味着缓冲是无用的。
如果你转向多用户模式,它也会引起真正的问题。
多个消费者的重点是平衡多个消费者的负载,以提高整体吞吐量。如果每个消费者只是抓住队列并处理其中的所有项目,那就不会发生这种情况。
在这种情况下的任何特定时间点,一次只有一个消费者会活跃。
(a)如果您只是使用迭代器来获取第一个元素,那么您的锁可能会有更精细的分辨率。
然而,这首先会使用迭代器失败,所以我认为这不太可能。