Hi,
> So, just to make sure I understood you correctly:
> If A < B means that thread A spawned thread B, then, if indeed A < B
> holds, sending a message to B would interrupt both A and B, sending A to
> sleep and making B process the message, causing it to implicitly wake up A
> again when it has finished processing?
No - A processes the message sent to B while B goes on with whatever it's
doing. I know it sounds wrong but it is not. Here's a dump from a test where
the following occurs:
1. Main process starts and spawns first thread.
2. First thread starts and spawns second thread.
3. Second thread enters endless loop of computing the same maths problem
over and over again.
4. First thread enters endless loop of sending two different messages to
second thread every few seconds. One message requests quick computation, the
other to compute the maths problem from step 3.
5. The message handler for the second thread calls the computation function
requested.
Dump:
Main process ready with thread ID 1048. <Step 1>
First thread ready with thread ID 1536. <Step 2>
Second thread ready with thread ID 696. <Step 3>
Second thread entering intensive process loop. <Step 3>
TID: 696 - intensive_process() start <Step 3>
TID: 1536, 11682718 ms - messaging UWM_INTENSIVE_PROCESSING to second thread
<Step 4>
TID: 1536, 11682718 ms - received UWM_INTENSIVE_PROCESSING
TID: 1536 - intensive_process() start <Step 5>
TID: 696 - intensive_process() end <Step 3 (cont.)>
TID: 696 - intensive_process() start <Step 3 (cont.)>
TID: 1536 - intensive_process() end <Step 5 end>
TID: 1536, 11685883 ms - messaging UWM_QUICK_PROCESSING to second thread
<Step 4 again>
TID: 1536, 11685883 ms - received UWM_QUICK_PROCESSING
TID: 1536 - quick_process() start
TID: 1536 - quick_process() end
TID: 1536, 11686734 ms - messaging UWM_INTENSIVE_PROCESSING to second thread
TID: 1536, 11686734 ms - received UWM_INTENSIVE_PROCESSING
TID: 1536 - intensive_process() start
TID: 696 - intensive_process() end
TID: 696 - intensive_process() start
TID: 1536 - intensive_process() end
TID: 1536, 11689848 ms - messaging UWM_QUICK_PROCESSING to second thread
TID: 1536, 11689848 ms - received UWM_QUICK_PROCESSING
...
So it's a little odd for thread A to send a message to thread B, only to end
up processing thread A!
If instead, the main process spawns both threads, the following occurs:
Main process ready with thread ID 1508.
First thread ready with thread ID 760.
Second thread ready with thread ID 744.
Second thread entering intensive process loop.
TID: 744 - intensive_process() start
TID: 760, 12356717 ms - messaging UWM_INTENSIVE_PROCESSING to second thread
TID: 1508, 12356717 ms - received UWM_INTENSIVE_PROCESSING
TID: 1508 - intensive_process() start
That is, the second thread starts its endless loop of computation, the first
thread sends messages, and the main process handles those messages.
> What about sending messages to thread A?
The rule is that the thread's creator is the thread that handles messages
for its spawn. So in both cases above, the main process/thread created
thread A, so no matter which thread sends a message to thread A, the main
process/thread is the handler for that message. The main process is what is
blocked.
> > 2. When a message is sent or posted, it disappears somewhere into
Windows,
> > which then makes the thread creator call the message handler of the
spawned
> > thread to process the message. As for when this is done, I have little
idea,
> > as while there I did declare and define a 'check for messages' message
loop
> > in the main process of my test app, I did not do it in the threads. The
> > message loop in the main process was not handling the messages to the
> > threads I created either. So the threads must have relied on Windows
which
> > provided a built-in message loop which jumped in to action whenever
there
> > was a message waiting.
>
> What were you doing in the threads? Just looping randomly, or doing system
> calls of sorts?
Hopefully this is covered in my example above. After x seconds, one thread
sent another a message saying what computation the receiving thread should
do. The receiving thread then called the associated function. There were
three types: quick_process() which simply returned, intensive_process()
which calculated about 100,000 prime numbers, and endless_process() which
was a function that never returned (obviously rarely used and only tested
for blocking).
> > However, if we wish to control this by always
> > defining a message loop, we can.
>
> And this would guarantee us that all messages would be processed exactly
> at the point we do the loop?
Yes, we can use PeekMessage() to see if there is a message waiting, and if
there is we can do something about it.
> > As I see it the best way to implement a Win32 event-driven sound server
> > would be as follows:
> > - still have the main process spawning a sound server as it currently
does
> > - the server is a Windows 'check for messages' message loop, which only
> > kicks into action when it receives a message (this removes the needs for
> > sleeps or something that would cause 100% CPU usage)
>
> I don't see what's so bad about using sleep()- as you pointed out, it's
> an elegant method not to cause 100% CPU usage. Is there some
> platform-specific issue I'm missing here?
Just that it's a loss of timing that we don't need, and can get around
(probably).
> Hmm, where is the periodic processing of MIDI data in this model?
I'm not sure how this currently happens in the program - does the current
sound server ask for data every so often? If we alter the model to use our
own message loop, we could put that in to occur every so often.
> > If this is all good, i'm very happy with this idea. Does this sound OK
and
> > can this work or fake-work under UNIX?
>
> I don't think I understand it yet- you intend to fork off threads to
> process sound commands; so far, that's fine (note that this probably won't
> even be neccessary in a shared address space). But you state that the
> server only kicks into action when it receives a message, and that is
> insufficient for playing music.
If the main process isn't what is getting the commands, then no. If the
sound server is getting commands and midi data from resources, then we would
have to have some sort of model that builds in a message loop, and perhaps
spawns off CPU-intensive tasks in threads. Or maybe two threads that check
for commands and data as well as spawned-off threads that process it. These
spawned-off threads would then send messages back to the server. Actually,
this is starting to sound more and more like a polled server - where do
events come in again?!
>
> > I neglected to mention something when sending messages for the purpose
of
> > passing pointers. This would be dangerous in our case because there is a
> > possibility (again, dependent on how this is all implemented), that
because
> > all this can be made to behave (at least, we hope) asynchronously, the
> > pointer may be destroyed by the time the message receiver tries to
access
> > it.
>
> Pointers would only be used for songs, but you raise a valid issue here-
> if the sound server thread would query the songs itself, we'd have to make
> the resource manager asynchronously re-entrant (using an abstracted
> sci_mutex API) and lock the resources (in order to prevent LRU
> management). This would also require a minor functional change to resource
> locking.
> The alternative would be to transfer the pointer and lock/unlock in the
> main thread, defining additional sound server messages to handle this (at
> least unlocking).
> > The problem is, if the code gets in and usually works, it's a lot
> > easier to just leave it without cleaning it up, commenting it well,
> > and going over it to check 'that one more time' for mistakes.
>
> Not if it just works 'usually'. Someone will take offense eventually and
> give the code its deserved beating. However, there's always the risk of
> old, unsafe and ugly code being forgotten if it works fine.
Or if the code has been in there for a while and it's nasty and badly
commented, people are less likely to want to fix it or may introduce bugs
when attempting to improve it.
> Interface information is what I personally consider to be most important-
> if you know what, globally speaking, a function does, it's usually easy to
> understand the code, or to replace it if the function is broken beyond
> hope.
> (Yes, the sound subsystem still hasn't been documented, I know.)
Agreed and agreed. :-)
> I know that the idea of good code being as easy to read as a comment
> fails to work from time to time, at least in the world of imperative
> languages. Therefore, I'd suggest this: Send me a list of places where you
> think comments would be in order in my code- it's always easier to
> poinpoint those places if you aren't the one responsible for the code- and
> I'll add those comments and try to get used to a more commented coding
> style this way.
> How about it?
Sounds good to me! And anyone else as well that notices something... One of
the things I have trouble with is that while I realise there is an SCI spec,
there doesn't seem to be a FreeSCI spec that I can find which explains how
things within FreeSCI interface with each other and work.
Cheers Christoph!
Alex.