Search code examples
multithreadingoperating-systemsemaphorerace-conditionproducer-consumer

Why no need to synchronize buffer when only have one producer and one consumer?


The following pseudo code comes from Wiki - producer-consumer problem.

semaphore fillCount = 0; // items produced
semaphore emptyCount = BUFFER_SIZE; // remaining space

procedure producer() 
{
    while (true) 
    {
        item = produceItem();
        down(emptyCount);
        putItemIntoBuffer(item);
        up(fillCount);
    }
}

procedure consumer() 
{
    while (true) 
    {
        down(fillCount);
        item = removeItemFromBuffer();
        up(emptyCount);
        consumeItem(item);
    }
}

My question is that why we can invoke putItemIntoBuffer and removeItemFromBuffer simultaneously without synchronization? Why is there no race condition here?


Solution

  • Why we can invoke putItemIntoBuffer and removeItemFromBuffer simultaneously without synchronization?

    The short answer: it is a pseudo code, so do not take it literally. The purpose of the code is to show how to synchronize the consumer and producer, not the data access, just like the Wiki article states at the beginning:

    The problem is to make sure that the producer won't try to add data into the buffer if it's full and that the consumer won't try to remove data from an empty buffer.

    The pseudo code you are referring solve this problem using semaphores:

    Semaphores solve the problem of lost wakeup calls.

    In case the buffer size is 1, the code might work just fine without any extra hassle. But the actual implementation of putItemIntoBuffer()/removeItemFromBuffer() for any buffer size might use locks internally to access shared buffer or could be implemented as a lock-less cyclic queues just like one of producer()/consumer() functions showed below in the article, i.e.:

    volatile unsigned int produceCount = 0, consumeCount = 0;
    ItemType buffer[BUFFER_SIZE];
    
    void putItemIntoBuffer(ItemType *item) {
        buffer[produceCount % BUFFER_SIZE] = *item;
        ++produceCount;
    }
    void removeItemFromBuffer(ItemType *item) {
        *item = buffer[consumeCount % BUFFER_SIZE];
        ++consumeCount;
    }
    

    Why is there no race condition here?

    There might be race conditions accessing shared buffer, but it is not a main point of the Producer–consumer problem and hence not the main point of the mentioned Wiki article.