我有一个应用程序有几个处理级别,如:
InputStream->Pre-Processing->Computation->OutputStream
这些实体中的每一个都在单独的线程中运行。 所以在我的代码中我有一般的线程,它拥有
std::vector<ImageRead> m_readImages;
然后它将此成员变量传递给每个线程:
InputStream input{&m_readImages};
std::thread threadStream{&InputStream::start, &InputStream};
PreProcess pre{&m_readImages};
std::thread preStream{&PreProcess::start, &PreProcess};
...
这些类中的每一个都拥有指向此数据的指针成员:
std::vector<ImageRead>* m_ptrReadImages;
我还定义了一个全局互斥锁,我在对该共享容器的每次读/写操作时锁定和解锁。 困扰我的是这种机制非常模糊,有时我会混淆数据是否被另一个线程使用。
那么在这些线程之间共享这个容器的更直接的方法是什么?
答案 0 :(得分:7)
您描述的过程&#34;输入 - &gt;预处理 - &gt;计算 - &gt;输出&#34;按顺序设计:每个步骤都依赖于前一个步骤,因此以这种特定方式并行化并不是有益的,因为每个线程只需等待另一个线程完成。试着找出哪个步骤需要花费大部分时间并将其并行化。或者尝试设置多个并行处理流水线,这些流水线按顺序在独立的单个数据集上运行。通常的方法是使用一个处理队列,在一组线程中分配任务。
答案 1 :(得分:1)
在我看来,您的阅读和预处理可以独立于容器完成。
天真地,我会将其构建为扇出,然后扇入任务网络。
首先,make dispatch task (一个任务是一个给予线程实际操作的工作单元),它将创建输入和预处理任务。
使用期货作为子任务的手段,以便将指针传回指向完全加载的图像。
创建第二个任务, std :: vector builder任务,只需在期货上调用join
即可在完成后获得结果并将其添加到{{1} } array。
我建议您以这种方式构建事物,因为我怀疑您正在进行的任何IO和预处理将比在向量中设置值花费更长的时间。使用任务而不是直接使用线程可以调整工作的并行部分。
我希望不要从具体元素中抽象出来。这是一种模式,我发现在可用硬件饱和之间可以很好地平衡,减少捶打/锁定争用,并且将来可以理解 - 稍后再调试它。
答案 2 :(得分:1)
我会使用3个单独的队列,ready_for_preprocessing
由InputStream提供并由预处理消耗,ready_for_computation
由预处理提供并由计算消耗,ready_for_output
它由Computation提供并由OutputStream使用。
您希望每个队列都在一个类中,该类具有访问互斥(用于控制实际添加和删除队列中的项目)和一个&#34;图像可用&#34;信号量(表示项目可用)以及实际队列。这将允许每个线程的多个实例。像这样:
class imageQueue
{
std::deque<ImageRead> m_readImages;
std::mutex m_changeQueue;
Semaphore m_imagesAvailable;
public:
bool addImage( ImageRead );
ImageRead getNextImage();
}
addImage()
获取m_changeQueue互斥锁,将图像添加到m_readImages,然后发出信号m_imagesAvailable;
getNextImage()
等待m_imagesAvailable。当它发出信号时,它需要m_changeQueue,从列表中删除下一个图像,然后返回它。
答案 3 :(得分:1)
忽略&#34;问题:如果每个操作都在一个单独的线程中运行,那么您想要处理的对象似乎从一个线程移动到另一个线程。实际上,它们一次只由一个线程唯一拥有(没有线程需要访问来自其他线程的任何数据)。有一种方法可以在C ++中表达:std::unique_ptr
。
然后每个步骤仅适用于其拥有的图像。您所要做的就是找到一种线程安全的方法,通过流程步骤逐个移动图像的所有权,这意味着关键部分只在任务之间的边界。由于你有多个这些,抽象它是合理的:
class ProcessBoundary
{
public:
void setImage(std::unique_ptr<ImageRead> newImage)
{
while (running)
{
{
std::lock_guard<m_mutex> guard;
if (m_imageToTransfer == nullptr)
{
// Image has been transferred to next step, so we can place this one here.
m_imageToTransfer = std::move(m_newImage);
return;
}
}
std::this_thread::yield();
}
}
std::unique_ptr<ImageRead> getImage()
{
while (running)
{
{
std::lock_guard<m_mutex> guard;
if (m_imageToTransfer != nullptr)
{
// Image has been transferred to next step, so we can place this one here.
return std::move(m_imageToTransfer);
}
}
std::this_thread::yield();
}
}
void stop()
{
running = false;
}
private:
std::mutex m_mutex;
std::unique_ptr<ImageRead> m_imageToTransfer;
std::atomic<bool> running; // Set to true in constructor
};
然后,流程步骤将要求使用getImage()
的图像,一旦该函数返回,它们就会唯一拥有该图像。他们处理它并将其传递给下一个setImage
的{{1}}。
您可以使用条件变量或在此类中添加队列来改进此功能,以便线程可以返回处理下一个图像。但是,如果某些步骤比其他步骤更快,那么最终它们必然会被较慢的步骤停滞。
答案 4 :(得分:1)
这是一个设计模式问题。我建议阅读并发设计模式,看看是否有任何可以帮助你的东西。
如果您想在以下顺序过程中添加并发性。
InputStream->Pre-Processing->Computation->OutputStream
然后我建议使用活动对象设计模式。这样,每个进程都不会被上一步阻塞,并且可以同时运行。实现起来也非常简单(这是一个实现: http://www.drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/225700095)
关于每个共享DTO的线程的问题。这可以通过DTO上的包装器轻松解决。包装器将包含写入和读取功能。 write函数块使用mutext,read返回const数据。
但是,我认为你的问题在于设计。如果流程是您所描述的顺序,那么为什么每个流程共享数据?一旦当前的数据完成,数据应该传递到下一个进程。换句话说,每个过程都应该解耦。
答案 5 :(得分:0)
使用互斥锁和锁是正确的。对于C ++ 11,这实际上是在线程之间访问复杂数据的最优雅方式。