If the producer threads are waiting on a numEmptySpaces
semaphore for access to the queue, this behaviour would probably happen anyway since it's unreasonable for the semaphore wait queue to be implemented in anything other than FIFO, but it's not guaranteed on most semaphore implementations.
Guaranteeing this kind of behaviour is very awkward because it's difficult to define the requirement of 'thread order':
How can you define which thread arrived first?
If the 'first thread' acquires some kind of lock that prevents other threads from proceeding, subsequent threads my herd in 'immediately' and so be subject to whatever lock-queue ordering is supplied by the OS.
The only thing I can think of is to force every producer thread to acquire a lock-free time-stamp or sequence-number before attempting to lock/queue anything. This could be done with a 'normal' atomic increment instruction. When a producer subsequently 'gets in' by acquiring a 'numEmptySpaces' unit and locks the queue, it can enqueue itself in sequence-number order onto the queue.
I'm not sure if a standard BlockingCollection
can be used for this. You may be able to 'OrderBy' the entries by sequence number, but I'm not sure that this operation locks the queue - it should do, but.. Besides, the sequenceNo would have to be added as a private memeber in a BlockingCollection descendant and the atomic increment result maintained as state for each task - you would have to add it to the Task
members.
I would be tempted to build by own BlockingQueue
class with a 'normal' queue, couple semaphores and a mutex to implement this, inserting new tasks in sequence-number order into the queue once the numEmptySpaces unit and queue mutex has been acquired. The atomic increment result could then be assembled into a stack/auto var.
This might be justified as an interview question, but I would have to be threatened with dismissal to actually implement it in production code. It's very difficult to think of a situation where it might be essential. The downsides of extra overhead and contention outweigh the dubious upside in everything I can think of.
I have similar reservations about attempting to explicitly maintain any sort of ordering at the dequeue/execute end. It would be messy to try and ensure that some 'checkpoint' in dequeued tasks was reached in sequence-number order. It would require cooperation from the task, which would need a private synchro object member to signal when it had reached its checkpoint. Don't ever try it:)