C#线程无需锁定生产者或消费者

时间:2013-04-01 09:28:51

标签: c# multithreading

TLDR;版本的主要问题:

  1. 使用线程时,用1个线程读取列表的内容是否安全,而另一个写入它是安全的,只要你不删除列表内容(reoganize order)并且只读取新对象完全添加新对象后

  2. 正在从" Old Value"更新Int。到"新价值"如果另一个线程读取此Int,则返回的值不是" Old Value"或"新价值"

  3. 线程是否可以跳过"跳过"一个关键区域,如果它忙,而不是只是睡觉,等待区域释放?

  4. 我有两段代码在单独的线程中运行,我想让一台代码作为另一台的生产者。我不想要任何一个线程"睡觉"在等待访问时,如果另一个线程正在访问它,则在其内部代码中跳过。

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


    我原来想要的伪代码流程。

    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++;
            }
    }
    

    看着这个,乍一看一切都很好看,我知道我不会从队列中检索一半构造的产品,所以即使是一个线程切换,列表的状态也不应该是一个问题生产者添加新对象时出现问题。这个假设是正确的,还是会有问题? (我的猜测是消费者要求列表中的特定位置,并且新对象被添加到最后,并且永远不会删除对象,这不会是一个问题)

    但是引起我注意的是,类似的问题可能会出现这样的问题&#34; counterProducer&#34;当它是&#34; counterProducer ++&#34;?时它是一个未知的值这可能导致临时值为&#34; null&#34;还是一些未知的价值?这会是一个潜在的问题吗?

    我的目标是在等待互斥锁时两个线程都没有锁定,而是继续它们的循环,这就是为什么我先做了上面的事情,因为没有锁定。

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


    替代伪代码以避免counterConsumer ++风险(需要帮助)。

    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++;
            }
    }
    

    所以代码的第二部分的目标,我无法弄清楚如何编写这个,是为了让两个类不等待另一个,以防另一个在&#34;关键区域&# 34;更新publicCounterProducer。如果我读取锁定功能正确,线程将进入休眠状态等待释放,这不是我想要的。可能最终不得不使用它,在这种情况下,第一个伪代码会这样做,只需设置一个&#34;锁定&#34;获得价值。

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

2 个答案:

答案 0 :(得分:5)

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

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

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

  4. 您应该真正查看System.Collections.Concurrent命名空间。特别要看BlockingCollection。它有一堆Try*运算符,您可以使用这些运算符来添加/删除集合中的项目而不会阻塞。

答案 1 :(得分:4)

  
    

使用线程时,使用1个线程读取列表内容是安全的,而另一个写入它是唯一的,只要你不删除列表内容(reoganize order)并且只在新对象完全添加后读取新对象

  

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

  
    

当一个线程将Int从“旧值”更新为“新值”时,是否存在风险,如果另一个线程读取此Int,则返回的值既不是“旧值”也不是“新值”

  

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

  
    

线程是否有可能在忙碌时“跳过”一个关键区域,而不是只是进入睡眠状态并等待区域释放?

  

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

顺便提一下,您是否有理由不使用内置生产者/消费者类,例如BlockingCollection<T>? BlockingCollection易于使用(在您阅读文档之后!),我建议您使用它。