3

I'd like to implement a buffer with single producer and a single consumer, where only the consumer may be blocked. Important detail here is that the producer can drop the update if the queue is full.

I've considered converting a wait-free implementation, but at first glance there seems to be no easy way to notify the consumer new data has arrived without losing notifications. So I settled on the very simple approach below, using a counting semaphore (some error handling details omitted for clarity):

Object ar[SIZE];
int head = 0, tail = 0;
sem_t semItems; // initialized to 0

void enqueue(Object o) {
    int val;
    sem_getvalue(&semItems, &val);
    if (val < SIZE - 1) {
        ar[head] = o;
        head = (head + 1) % SIZE;
        sem_post(&semItems);
    }
    else {
       // dropped
    }
}
Object dequeue(void) {
    sem_wait(&semItems);
    Object o = ar[tail];
    tail = (tail + 1) % SIZE;
    return o;
}

Are there any safety issues with this code? I was surprised not to see an implementation like it anywhere in the popular literature. An additional question is whether sem_post() would ever block (calls futex_wake() under the hood in linux). Simpler solutions are also welcome of course.

edit: edited code to leave a space between reader and writer (see Mayurk's response).

wds
  • 31,873
  • 11
  • 59
  • 84
  • Can i know why do you need to block consumer only? – Sumit Gemini Nov 23 '16 at 06:58
  • @SumitGemini when there's a lot of traffic, the consumer should run slower than the producer, since the producer talks to hardware. This is just one possible approach I'm looking at, it could be that the implicit CAS loops actually make it worse. – wds Nov 23 '16 at 08:40
  • @SumitGemini just came across a proposal for native C++ semaphores that explains it better: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2043.html#SemaphoreTypes – wds Nov 25 '16 at 02:30

1 Answers1

2

I can see one problem in this implementation. Consider the following sequence.

  1. Assume buffer is full, but consumer is not yet started. So (head=0, tail=0, sem_val=SIZE).
  2. dequeue() is called from consumer thread. sem_wait() succeeds. So just at that instance (head=0, tail=0, sem_val=SIZE-1). Consumer starts reading ar[0].
  3. Now there is a thread switch. enqueue() is called from producer thread. sem_getvalue() would return SIZE-1. So producer writes at ar[0].

Basically I think you need mutex protection for reading and writing operations. But adding mutex might block the threads. So I am not sure whether you get expected behavior from this logic.

MayurK
  • 1,925
  • 14
  • 27
  • You are right, but isn't this easily fixed by sacrificing one location in the buffer, i.e. ensure val < SIZE-1? – wds Nov 23 '16 at 08:32
  • @wds No. You might face this issue at any time. Example (head=3, tail=3,sem_val=SOMTHING). Even in this case if the above sequence of operation leads to corruption of buffer. – MayurK Nov 23 '16 at 08:38
  • dequeue can only read from a location in the buffer after enqueue has "published" it by posting on the semaphore. I don't see how the situation you allude to can ever exist. – wds Nov 23 '16 at 08:46
  • If after sem_wait(), before tail value update thread switch happens, you will endup with this issue. – MayurK Nov 23 '16 at 08:50
  • Can you spell this out for me with an example? There is only one reader, so why is the value of tail an issue? – wds Nov 23 '16 at 09:17
  • @wds Sorry. You are right. Making that change in if(val <..) solves this issue. But you can fill SIZE-1 items. You can go ahead with that logic. If you want to fill SIZE number of items you can take the edits I have made in your code. – MayurK Nov 23 '16 at 09:24
  • 1
    your solution suffers from the same issue. If the buffer is full, then you can have `h=0, t=1, rem=size`, execute `sem_wait`, now `enqueue()` will write to `ar[1]` while `dequeue()` might be reading from it. – wds Nov 25 '16 at 02:37
  • @wds Oh yes. I am wrong again. I removed it. I think your solution of checking if (val < SIZE - 1) is the only one which works. But only problem is you can not have SIZE=1. – MayurK Nov 25 '16 at 04:39