PDA

View Full Version : QSemaphore: 1 Producer, 3 Consumers



Daedalus
30th March 2007, 16:58
Hi,

the basic problem I was facing is described here (http://www.qtcentre.org/forum/f-newbie-4/t-software-layout-multithreading-qworkspace-fileoperations-6254.html)

As my work progressed, I faced the need to link 1 producer thread to 3 consumer-threads derived from the same class.

In my Widget Class, the semaphore and buffer are declared as followed (based on the Semaphore example and a single consumer), which worked:



QStringList threadBuffer[300];

QSemaphore *freeListSlots(300);
QSemaphore *usedListSlots;

Importer *importerThread;
Converter *converterThread1;
Converter *converterThread2;
Converter *converterThread3;


My idea was, to let thread1 work on buffer[0] to buffer[99], thread2 on buffer[100] to buffer[199] and so on. The thread, that has no accessible data, should block until new data arrives.

My "emergency plan" is, to use a different semaphore for each thread and provide a pointer to it in the consumer-constructor, then acquire and release based on the actual buffer-position. But if there was a nicer way, I'd prefer the alternative :)

Greets,
Daedalus

wysota
30th March 2007, 20:27
If each thread works on a different buffer, what do you need semaphores for? Either make all threads work on the whole buffer or use a wait condition.

Daedalus
31st March 2007, 08:28
The worker constantly fills the (circular) buffer. What is not clear to me from the documentation is, how to work with a number of reading threads on this buffer without running into undefined parts of it or reading data twice.

For one consumer thread, my code was:



Converter::run()
{
int converted = 0;

while(!abort){
parentWindow->usedListSlots->acquire();
processEntry(threadBuffer[converted]);
parentWindow->freeListSlots->release();
converted++;
if (converted==300){
converted=0;
}
}
}

The semaphore has a max value of 300, one for each item.

if I run 3 of these threads, I get random crashes, while the import/convert-loops are processed. So I thought, I'd have to work on different parts of the same buffer simultaneously.

But maybe I just got something completely wrong from the example.

jacek
31st March 2007, 15:39
The semaphore has a max value of 300, one for each item.
Which means that all three threads can acquire it simultaneously, which is not safe, since the docs doesn't say that QStringList is thread-safe. Only one thread should be accessing the buffer at a time (unless you'll have a buffer that can be read simultaneously by several threads) and for this you need a mutex.

The Converter::run() should look more or less like this:

Converter::run()
{
while( ! abort ) {
QString entry( buffer->get() );
if( ! abort ) {
processEntry( entry );
}
}
}

Now you have to implement a Buffer class with get() and put() methods which will use proper synchronisation mechanisms (for example a mutex and wait conditions or mutex and semaphores).

Such approach doesn't depend on number of threads that operate on a buffer. You can have several consumers and several producers at the same time.

wysota
31st March 2007, 16:48
Furthermore if you use QReadWriteLock with a proper buffer implementation (for example a circular buffer with separate indexes for reading and writing), you'll be able to consume and produce at the same time. Here you could use a semaphore to make sure you avoid buffer underruns.

Daedalus
1st April 2007, 08:47
Thank you for your explanation :). In my opinion, the documentation is a bit short regarding this point.

wysota
1st April 2007, 09:16
Just one thing - Jacek and I talked about this yesterday and he said using a read write lock won't help in this situation as a writer would block all readers. Of course he's right.

Daedalus
1st April 2007, 19:15
I think, as the writer just performs a RegEx operation and some checks, it wouldn't slow down processing too much, as the reader needs a lot of time creating AbstractModelItems from the gathered data (about 38000 from a ( rather small ;) ) 2.000.000 line file, in a productive environment it may be 8-10 times that many). But to make sure, that every thread finished working on the buffer, before unblocking the writer would create longer delays I think, as some threads may just idle away, waiting for all other readers to finish.

jacek
1st April 2007, 19:42
In my opinion, the documentation is a bit short regarding this point.
I think it's because there is no point in duplicating general knowledge in the docs. If you read any handbook for academic course on operating systems, you'll know how to use all of the synchronisation mechanisms, regardless which library implements them.


to make sure, that every thread finished working on the buffer, before unblocking the writer would create longer delays I think, as some threads may just idle away, waiting for all other readers to finish.
If each reader thread will block access to the buffer until it finishes processing its entry, you'll end up with one working thread and two idle ones all of the time. The key to the success in parallel processing is to limit the synchronisation to necessary minimum, because inside the synchronisation point you can have only sequential processing.

Note that all the reader thread needs from the buffer is just a single string, so you have to block access to the buffer only for time needed to read that string and until the thread finishes processing that string it doesn't have to access the buffer, so in that time other threads can read from the buffer.

wysota
1st April 2007, 20:59
Remember that it's better to protect data than to protect code, so synchronise only when you really access the shared data and release the lock right away after you're done with it. You can easily allow simoultaneous reading and writing to the buffer, the only problem is to synchronise all readers and all writers.