Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Threading without locking Producer or Consumer

TLDR; version of the main questions:

  1. While working with threads, is it safe to read a list's contents with 1 thread, while another write to it, as long you do not delete list contents (reoganize order) and only reads new object after the new object is added fully

  2. While an Int is being updated from "Old Value" to "New Value" by one thread, is there is a risk, if another thread reads this Int that the value returned is neither "Old Value" or "New Value"

  3. Is it possible for a thread to "skip" a critical region if its busy, instead of just going to sleep and wait for the regions release?

I have 2 pieces of code running in seperate threads and I want to have the one act as a producer for the other. I do not want either thread "sleeping" while waiting for access, but instead skip forward in their internal code if the other thread is accessing this.

My original plan were to share the data via this approach (and once counter got high enough switch to a secondary list to avoid overflows).


pseudo code of flow as I original intended it.

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

Looking at this, everything looked fine at first glance, I knew I would not be retrieving a half constructed product from the queue, so the status of the list regardless of where it is should not be a problem even if a thread switch occour while the Producer is adding a new object. Is this assumption correct, or can there be problems here? (my guess is as the consumer is asking for a specific location in the list and new objects are added to the end, and objects are never deleted that this will not be a problem)

But what caught my eye was, could a similar problem occour that "counterProducer" is at an unknown value while it is being "counterProducer++"? Could this result in the value temporary be "null" or some unknown value? Will this be a potential issue?

My goal is to have neither of the two threads lock while waiting for a mutex but instead continue their loops, which is why I made the above first, as there is no locking.

If the usage of the list will cause problems, my workaround will be to make a linked list implementation, and share it between the two classes, still use the counters to see if new work has been added and keep last location while the personalQueue moves new stuff to personal queue. So producer add new links, consumer reads them, and deletes previous. (no counter on the list, just external counters to know how much has been added and removed)


alternative pseudo code to avoid the counterConsumer++ risk (need help with this).

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

So goal in the 2nd part of code, and I have not been able to figure out how to code this, is to make both classes not wait for the other in case the other is in the "critical region" of updating the publicCounterProducer. If I read the lock functionality correct, the threads will go to sleep waiting for the release, which is not what I want. Might end up with having to use it though, in which case, first pseudocode would do it, and just set a "lock" on the getting of the value.

Hope you can help me out with my many questions.

like image 982
Taoh Avatar asked Jan 13 '23 19:01

Taoh


1 Answers

  1. No it is not safe. A context switch can occur within .Add after List has added the object, but before List has updated the internal data structure.

  2. If it is int32, or if it is int64 and you are running in an x64 process, then there is no risk. But if you have any doubts, use the Interlocked class.

  3. Yes, you can use a Semaphore, and when it is time to enter the critical region, use WaitOne overload that takes a timeout. Pass a timeout of 0. If WaitOne returns true, then you successfully acquired the lock and can enter. If it returns false, then you did not acquire the lock and should not enter.

You should really look at the System.Collections.Concurrent namespace. In particular, look at the BlockingCollection. It has a bunch of Try* operators you can use to add/remove items from the collection without blocking.

like image 200
Brandon Avatar answered Jan 18 '23 23:01

Brandon