不锁定生产者或消费者的C#线程

本文关键字:线程 消费者 锁定 生产者 | 更新日期: 2023-09-27 18:06:54

TLDR;主要问题的版本:

  1. 在使用线程时,如果不删除列表内容(重新组织顺序(,并且只在新对象完全添加后读取新对象,那么用1个线程读取列表内容,而另一个线程写入列表内容是否安全

  2. 当一个线程将Int从"Old Value"更新为"New Value"时,如果另一个线程读取该Int,则返回的值既不是"Old Value"也不是"New Value

  3. 线程是否可以在繁忙的情况下"跳过"一个关键区域,而不是仅仅进入睡眠状态等待区域释放?

我有两段代码在不同的线程中运行,我想让其中一段充当另一段的生产者。我不希望任何一个线程在等待访问时"休眠",而是在另一个线程正在访问时跳过它们的内部代码。

我最初的计划是通过这种方法共享数据(一旦计数器足够高,就切换到辅助列表以避免溢出(。


流的伪代码,正如我最初打算的那样。

Producer
{
Int counterProducer;
bufferedObject newlyProducedObject;
List <buffered_Object> objectsProducer;
    while(true) 
    {
        <Do stuff until a new product is created and added to newlyProducedObject>;
        objectsProducer.add(newlyProducedObject_Object);
        counterProducer++
    }
}

Consumer
{
Int counterConsumer;
Producer objectProducer; (contains reference to Producer class)
List <buffered_Object> personalQueue
    while(true)
        <Do useful work, such as working on personal queue, and polish nails if no personal queue>
        //get all outstanding requests and move to personal queue
        while (counterConsumer < objectProducer.GetcounterProducer())
        {
            personalQueue.add(objectProducer.GetItem(counterconsumer+1));
            counterConsumer++;
        }
}

考虑到这一点,乍一看一切都很好,我知道我不会从队列中检索半成品,所以列表的状态(无论它在哪里(应该不会成为问题,即使在Producer添加新对象时发生了线程切换。这个假设是正确的,还是可能存在问题?(我的猜测是,当消费者要求在列表中指定一个特定的位置,并且新的对象被添加到末尾,并且对象从未被删除时,这不会是一个问题(

但引起我注意的是,当"counterProducer"是"counterProduction++"时,是否会出现类似的问题?这会导致临时值为"null"或某个未知值吗?这会是一个潜在的问题吗?

我的目标是在等待互斥锁时,两个线程都不锁定,而是继续它们的循环,这就是为什么我首先进行了上述操作,因为没有锁定。

如果列表的使用会引起问题,我的解决方法是制作一个链表实现,并在两个类之间共享,仍然使用计数器查看是否添加了新工作,并在personalQueue将新工作移动到个人队列时保留最后一个位置。所以生产者添加新的链接,消费者读取它们,并删除以前的链接。(列表中没有计数器,只有外部计数器来了解添加和删除了多少(


避免反消费者++风险的替代伪代码(需要帮助(。

Producer
{
Int publicCounterProducer;
Int privateCounterProducer;
bufferedObject newlyProducedObject;
List <buffered_Object> objectsProducer;
    while(true) 
    {
        <Do stuff until a new product is created and added to newlyProducedObject>;
        objectsProducer.add(newlyProducedObject_Object);
        privateCounterProducer++
        <Need Help: Some code that updates the publicCounterProducer to the privateCounterProducer if that variable is not 
locked, else skips ahead, and the counter will get updated at next pass, at some point the consumer must be done reading stuff, and 
new stuff is prepared already>      
    }
}

Consumer
{
Int counterConsumer;
Producer objectProducer; (contains reference to Producer class)
List <buffered_Object> personalQueue
    while(true)
        <Do useful work, such as working on personal queue, and polish nails if no personal queue>
        //get all outstanding requests and move to personal queue
        <Need Help: tries to read the publicProducerCounter and set readProducerCounter to this, else skips this code>
        while (counterConsumer < readProducerCounter)
        {
            personalQueue.add(objectProducer.GetItem(counterconsumer+1));
            counterConsumer++;
        }
}

因此,代码第二部分的目标是,如果另一个处于更新publicCounterProducer的"关键区域",则使两个类都不等待另一个,而我还没有弄清楚如何对此进行编码。如果我正确读取了锁功能,线程将进入睡眠状态,等待释放,这不是我想要的。不过,最终可能不得不使用它,在这种情况下,第一个伪代码会执行此操作,并在获取值时设置一个"锁"。

希望你能帮我解决许多问题。

不锁定生产者或消费者的C#线程

  1. 不,它不安全。上下文切换可以在List添加对象之后、但在List更新内部数据结构之前在.Add内发生。

  2. 如果是int32,或者是int64,并且您正在x64进程中运行,则没有风险。但是,如果您有任何疑问,请使用Interlocked类。

  3. 是的,您可以使用Semaphore,当需要进入关键区域时,使用需要超时的WaitOne重载。传递超时0。如果WaitOne返回true,则表示您成功获取了锁,可以进入。如果返回false,则表示您没有获得锁,不应进入。

你真的应该看看这个系统。集合。并发命名空间。特别是BlockingCollection。它有一组Try*运算符,您可以使用这些运算符在不阻塞的情况下添加/删除集合中的项。

在使用线程时,如果不删除列表内容(重新组织顺序(,并且只在新对象完全添加后读取新对象,那么用1个线程读取列表内容,而另一个线程写入列表内容是否安全

不,不是。将项添加到列表的副作用可能是重新分配其基础数组。List<T>的当前实现在将旧数据复制到内部引用之前更新内部引用,因此多个线程可能会看到一个大小正确但不包含数据的列表。

当一个线程将Int从"Old Value"更新为"New Value"时,如果另一个线程读取该Int,则返回的值既不是"Old Value"也不是"New Value

不,int更新是原子的。但如果两个线程同时递增counterProducer,就会出错。您应该使用Interlocked.Increment()来增加它。

线程是否可以在繁忙的情况下"跳过"一个关键区域,而不是仅仅进入睡眠状态等待区域释放?

不可以,但您可以使用(例如(WaitHandle.WaitOne(int)来查看等待是否成功,并相应地进行分支。WaitHandle由几个同步类实现,例如ManualResetEvent

顺便说一句,您不使用内置的生产者/消费者类(如BlockingCollection<T>(有什么原因吗?BlockingCollection很容易使用(在您阅读文档之后!(,我建议您改用它。