两个线程如何以最小的阻塞访问公共缓冲区数组? (c#)

发布于 2024-08-26 17:48:52 字数 559 浏览 5 评论 0原文

我正在开发一个图像处理应用程序,在主线程之上有两个线程:

1 - CameraThread 从网络摄像头捕获图像并将其写入缓冲区

2 - ImageProcessingThread< /strong> 从该缓冲区获取最新图像进行过滤。

之所以采用多线程,是因为速度至关重要,我需要 CameraThread 继续抓取图片,并在 ImageProcessingThread 仍在处理上一张图像的同时,准备好最新的捕获内容以供 ImageProcessingThread 拾取。

我的问题是找到一种快速且线程安全的方法来访问该公共缓冲区,我认为理想情况下,它应该是一个三重缓冲区(图像[3]),这样如果 ImageProcessingThread 很慢,那么 CameraThread 可以继续运行在另外两个图像上书写,反之亦然。

什么样的锁定机制最适合线程安全?

我查看了 lock 语句,但似乎它会使一个线程阻塞等待另一个线程完成,这违背了三重缓冲的观点。

预先感谢您的任何想法或建议。

J。

I'm working on an image processing application where I have two threads on top of my main thread:

1 - CameraThread that captures images from the webcam and writes them into a buffer

2 - ImageProcessingThread that takes the latest image from that buffer for filtering.

The reason why this is multithreaded is because speed is critical and I need to have CameraThread to keep grabbing pictures and making the latest capture ready to pick up by ImageProcessingThread while it's still processing the previous image.

My problem is about finding a fast and thread-safe way to access that common buffer and I've figured that, ideally, it should be a triple buffer (image[3]) so that if ImageProcessingThread is slow, then CameraThread can keep on writing on the two other images and vice versa.

What sort of locking mechanism would be the most appropriate for this to be thread-safe ?

I looked at the lock statement but it seems like it would make a thread block-waiting for another one to be finished and that would be against the point of triple buffering.

Thanks in advance for any idea or advice.

J.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

梦过后 2024-09-02 17:48:52

这可能是生产者-消费者模式的教科书示例。

如果您要使用 .NET 4,则可以使用 IProducerConsumerCollection和关联的具体类来提供您的功能。

如果没有,请阅读 本文了解有关该模式的更多信息,以及此问题可指导您编写阻塞先进先出结构的线程安全实现。

This could be a textbook example of the Producer-Consumer Pattern.

If you're going to be working in .NET 4, you can use the IProducerConsumerCollection<T> and associated concrete classes to provide your functionality.

If not, have a read of this article for more information on the pattern, and this question for guidance in writing your own thread-safe implementation of a blocking First-In First-Out structure.

那小子欠揍 2024-09-02 17:48:52

就我个人而言,我认为您可能想为此考虑一种不同的方法,而不是写入必须管理访问的集中式“缓冲区”,您可以切换到使用事件的方法吗?一旦相机线程“接收到”图像,它就可以引发一个事件,将图像数据传递给实际处理图像处理的进程。

另一种方法是使用队列,该队列是一个 FIFO(先进先出)数据结构,现在它的访问不是线程安全的,因此您必须锁定它,但您的锁定时间将非常短将项目放入队列中。您还可以使用其他线程安全的队列类。

使用您的方法时,您必须解决许多问题。访问阵列时发生阻塞、用完可用阵列槽后发生的情况的限制、阻塞等。

Personally I think you might want to look at a different approach for this, rather than writing to a centralized "buffer" that you have to manage access to, could you switch to an approach that uses events. Once the camera thread has "received" an image it could raise an event, that passed the image data off to the process that actually handles the image processing.

An alternative would be to use a Queue, which the queue is a FIFO (First in First Out) data structure, now it is not thread-safe for access so you would have to lock it, but your locking time would be very minimal to put the item in the queue. There are also other Queue classes out there that are thread-safe that you could use.

Using your approach there are a number of issues that you would have to contend with. Blocking as you are accessing the array, limitations as to what happens after you run out of available array slots, blocking, etc..

檐上三寸雪 2024-09-02 17:48:52

考虑到图片所需的处理量,我认为简单的锁定方案不会成为您的瓶颈。在开始在错误的问题上浪费时间之前先进行衡量。
使用“无锁”解决方案时要非常小心,它们总是比看起来更复杂。

你需要一个队列,而不是数组。
如果你可以使用 dotNET4,我会使用 ConcurrentQuue。

Given the amount of precessing needed for a picture, I don't think that a simple locking scheme would be your bottleneck. Measure before you start wasting time on the wrong problem.
Be very careful with 'lock-free' solutions, they are always more complicated than they look.

And you need a Queue, not an array.
If you can use dotNET4 I would use the ConcurrentQuue.

梦途 2024-09-02 17:48:52

您将必须运行一些性能指标,但请查看无锁队列

例如,请参阅此问题及其相关答案

然而,在您的特定应用程序中,您的处理器只对最新的图像真正感兴趣。实际上,这意味着您实际上只想维护两个项目(新项目和前一个项目)的队列,以便读取和写入之间不存在争用。例如,一旦写入新条目,您可以让生产者从队列中删除旧条目。

编辑:说了这么多,我认为米切尔·塞勒斯的回答有很多优点。

You will have to run some performance metrics, but take a look at lock free queues.

See this question and its associated answers, for example.

In your particular application, though, you processor is only really interested in the most recent image. In effect this means you only really want to maintain a queue of two items (the new item and the previous item) so that there is no contention between reading and writing. You could, for example, have your producer remove old entries from the queue once a new one is written.

Edit: having said all this, I think there is a lot of merit in what is said in Mitchel Sellers's answer.

伴我心暖 2024-09-02 17:48:52

我会考虑使用 ReaderWriterLockSlim 它允许快速用于写入的读锁和可升级锁。

I would look at using a ReaderWriterLockSlim which allows fast read and upgradable locks for writes.

如梦亦如幻 2024-09-02 17:48:52

这不是对您的问题的直接答案,但最好重新考虑您的并发模型。锁是同步任何东西的糟糕方法——级别太低、容易出错等等。尝试根据 消息传递并发

这里的想法是每个线程都是它自己的紧密包含的消息循环,并且每个线程都有一个用于发送和接收消息的“邮箱”——我们将使用术语 MailboxThread将这些类型的对象与普通的简线程区分开来。

因此,您不再有两个线程访问同一缓冲区,而是有两个 MailboxThreads 在彼此之间发送和接收消息(伪代码):

let filter =
    while true
        let image = getNextMsg() // blocks until the next message is recieved
        process image

let camera(filterMailbox) =
    while true
        let image = takePicture()
        filterMailbox.SendMsg(image) // sends a message asyncronous

let filterMailbox = Mailbox.Start(filter)
let cameraMailbox = Mailbox.Start(camera(filterMailbox))

现在您正在处理的线程根本不知道或不关心任何缓冲区。他们只是等待消息并在可用时处理它们。如果您发送多条消息供 filterMailbox 处理,这些消息将排队等待稍后处理。

这里最困难的部分实际上是实现 MailboxThread 对象。尽管需要一些创造力才能正确实现,但完全有可能实现这些类型的对象,以便它们仅在处理消息时保持线程打开,并在处理消息时将执行线程释放回线程池没有消息需要处理(此实现允许您终止应用程序而无需悬空线程)。

这里的优点是线程如何发送和接收消息而无需担心锁定或同步。在幕后,您需要在消息入队或出队之间锁定消息队列,但该实现细节对您的客户端代码是完全透明的。

This isn't a direct answer to your question, but it may be better to rethink your concurrency model. Locks are a terrible way to syncronize anything -- too low level, error prone, etc. Try to rethink your problem in terms of message passing concurrency:

The idea here is that each thread is its own tightly contained message loop, and each thread has a "mailbox" for sending and receiving messages -- we're going to use the term MailboxThread to distinguish these types of objects from plain jane threads.

So instead of having two threads accessing the same buffer, you instead have two MailboxThreads sending and receiving messages between one another (pseudocode):

let filter =
    while true
        let image = getNextMsg() // blocks until the next message is recieved
        process image

let camera(filterMailbox) =
    while true
        let image = takePicture()
        filterMailbox.SendMsg(image) // sends a message asyncronous

let filterMailbox = Mailbox.Start(filter)
let cameraMailbox = Mailbox.Start(camera(filterMailbox))

Now you're processing threads don't know or care about any buffers at all. They just wait for messages and process them whenever they're available. If you send to many message for the filterMailbox to handle, those messages get enqueued to be processed later.

The hard part here is actually implementing your MailboxThread object. Although it requires some creativity to get right, its wholly possible to implement these types of objects so that they only hold a thread open while processing a message, and release the executing thread back to the thread-pool when there are no messages left to handle (this implementation allows you to terminate your application without dangling threads).

The advantage here is how threads send and receive messages without worrying about locking or syncronization. Behind the scenes, you need to lock your message queue between enqueing or dequeuing a message, but that implementation detail is completely transparent to your client-side code.

夏日浅笑〃 2024-09-02 17:48:52

只是一个想法。

由于我们只讨论两个线程,因此我们可以做出一些假设。

让我们使用您的三重缓冲区想法。假设只有 1 个写入器和 1 个读取器线程,我们可以以整数的形式来回抛出一个“标志”。两个线程将不断旋转但更新其缓冲区。
警告:这仅适用于 1 读取器线程

伪代码

共享变量:

int Status = 0; //0 = 准备写入; 1 = 准备读取

Buffer1 = 新字节[]

Buffer2 = 新字节[]

Buffer3 = 新字节[]

BufferTmp = null

thread1
{

while(true)
{
    WriteData(Buffer1);
    if (Status == 0)
    {
        BufferTmp = Buffer1;
        Buffer1 = Buffer2;
        Buffer2 = BufferTmp;
        Status = 1;
    }
}

}

线程2
{

while(true)
{
    ReadData(Buffer3);
    if (Status == 1)
    {
        BufferTmp = Buffer1;
        Buffer2 = Buffer3;
        Buffer3 = BufferTmp;
        Status = 0;
    }
}

}

请记住,您的 writedata 方法不会创建新的字节对象,而是更新当前的字节对象。创建新对象的成本很高。

另外,您可能需要 ELSE 语句中的 thread.sleep(1) 与 IF 语句一起使用,否则在单核 CPU 中,旋转线程会增加其他线程调度之前的延迟。例如。在调度读取线程之前,写入线程可能会运行自旋 2-3 次,因为调度程序会看到写入线程正在“工作”

Just an Idea.

Since we're talking about only two threads, we can make some assumptions.

Lets use your tripple buffer idea. Assuming there is only 1 writer and 1 reader thread, we can toss a "flag" back-and-forth in the form of an integer. Both threads will continuously spin but update their buffers.
WARNING: This will only work for 1 reader thread

Pseudo Code

Shared Variables:

int Status = 0; //0 = ready to write; 1 = ready to read

Buffer1 = New bytes[]

Buffer2 = New bytes[]

Buffer3 = New bytes[]

BufferTmp = null

thread1
{

while(true)
{
    WriteData(Buffer1);
    if (Status == 0)
    {
        BufferTmp = Buffer1;
        Buffer1 = Buffer2;
        Buffer2 = BufferTmp;
        Status = 1;
    }
}

}

thread2
{

while(true)
{
    ReadData(Buffer3);
    if (Status == 1)
    {
        BufferTmp = Buffer1;
        Buffer2 = Buffer3;
        Buffer3 = BufferTmp;
        Status = 0;
    }
}

}

just remember, you're writedata method wouldn't create new byte objects, but update the current one. Creating new objects is expensive.

Also, you may want a thread.sleep(1) in an ELSE statement to accompany the IF statements, otherwise one a single core CPU, a spinning thread will increase the latency before the other thread gets scheduled. eg. The write thread may run spin 2-3 times before the read thread gets scheduled, because the schedulers sees the write thread doing "work"

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文