Re: design Q : using timer/threads
From: Joseph M. Newcomer (newcomer_at_flounder.com)
Date: 10/30/04
- Next message: Chris: "DECLARE_DYNCREATE and related stuff"
- Previous message: ak: "Re: default font in a MFC program (main menu)"
- In reply to: Joan Coleman: "Re: design Q : using timer/threads"
- Next in thread: Scott McPhillips [MVP]: "Re: design Q : using timer/threads"
- Messages sorted by: [ date ] [ thread ]
Date: Sat, 30 Oct 2004 05:47:35 -0400
Essentially, you can forget about the possibility of sampling at 1ms intervals. On a good
day, on an unloaded machine, with a great deal of good fortune, you might be able to do it
in short bursts using the MM timer, but bottom line is that hardware like this is simply
unsuited for interfacing to Windows. The scheduler can preempt you for random amounts of
time any time it feels like it; all kernel threads are running at higher priority than any
user thread. Having a thread polling is a guarantee that you will get abysmal performance,
a very sluggish GUI, and generally unacceptable performance.
You have not said where the data is stored, and using a critical section to do this
synchronization is asking for serious performance hits...tens of milliseconds delay if you
can't get the lock. You need a LOT better way of handling this for such high performance.
The best thing to do would be to have a piece of hardware that interrupted you itself
every 1ms, and do all the work in a device driver, using a very large internal buffer, At
this point, you are deep in the kernel, and you have very low response latency. It is
barely doable under these conditions.
The whole structure is still deeply flawed. "Start worker thread 2 to write to disk" is
already asking more than is necessary. Use a semaphore for this, and a queue, or better
still, use asynchrononous I/O. You would never "start" a thread to do the I/O, and there
is no need to use a thread to do the I/O.
Your algorithm should be:
callback:
if buffer needed, allocate one
read sample
add to buffer
if buffer full, QUEUE UP FOR WRITING
No critical section is required. No worker thread is required to handle the I/O.
There is far too much complexity in this design. It is a reasonably straightforward
problem: a 100ms timer event that sends out a request for data, which is satisified some
time in the future by the device responding, and one thread which once a ms reads one
sample of data, and mostly gets it every ms. All this buffering and disk-writing-thread
and synchronization stuff is largely unnecessary, since it can be handled more readily
without all the mechanisms you suggest.
You do not make it clear what you mean by "polling". Do you mean that once every 100ms you
send them a request to deliver a result, which is fine, or do you mean that you have to
keep asking "is there data ready?"
Networks do not require polling. Networks notify you when data arrives.
You don't need a thread to do the disk write; you can use asynchronous I/O and do it
yourself in the same thread.
You don't need to queue a buffer up; an asynchronous WriteFile works real well.
You can have a thread that handles I/O completion events. It doesn't need any
synchronization, either, except to free the buffer, and you don't need to do that
yourself; it is built into the allocator.
callback:
global state to this function, for example, inherited from a parameter, would be
LARGE_INTEGER fp; // file position, initialize .QuadPart to 0
MyDiskBuffer buffer = NULL; // current buffer
HANDLE file; // open with FILE_FLAG_OVERLAPPED
if(buffer == NULL)
{
buffer = new MyDiskBuffer(file, fp);;
// or
// buffer = (MyDiskBuffer *)HeapAlloc(heap, sizeof(MyDiskBuffer);
}
if(buffer == NULL)
{ /* allocation failed, we are in trouble */
shut down this thread and get out
} /* allocation failed, we are in trouble */
MYVALUE data = ....read data item ...;
switch(buffer.WriteData(data))
{ /* write */
case MyDiskBuffer::OK:
break;
case MyDiskBuffer::Full:
if(!buffer.WriteToDisk(fp))
...deal with error, shut down thread and exit
buffer = NULL;
break;
case MyDiskBuffer::Error:
return; // get out of thread, we're messed up
} /* write */
class MyDiskBuffer : public OVERLAPPED {
public:
MyDiskBuffer(HANDLE fh, LARGE_INTEGER fp) {
pos = 0;
h = fh;
offset = fp.LowPart; offsetHigh = fp.HighPart; }
typedef enum { OK, Full, Error } WriteResult;
WriteResult WriteData(MYVALUE data) {
samples[pos] = data;
pos++;
if(pos == MAX_SAMPLES)
return Full;
return OK;
}
BOOL WriteToDisk(LARGE_INTEGER & fp) {
DWORD bytesWritten;
fp.QuadPart += pos * sizeof(MYVALUE);
return WriteFile(h, samples, pos * sizeof(MYVALUE),
&bytesWritten,
(LPOVERLAPPED)this);
}
protected:
HANDLE h;
DWORD pos;
MYVALUE samples[MAX_SAMPLES];
};
the completion port handler is as described in my essay on I/O completion ports. It will
free the overlapped structure back to where it came from.
But the presence of a disk write will quite possibly induce random pauses that will exceed
the 1ms window, while a disk thread deals with lazy writeback in the distant future
(hundreds of milliseconds after you initiate the I/O). Just don't ever believe you are
going to get regular 1ms samples.
It is not clear why you will need an array of buffers, since the synchronization to
allocate from an array of buffers is going to have the same performance as you would get
using a private heap, so why make life more complex than it needs to be? Use new or
HeapAlloc and avoid gratuitous complexity.
Frankly, I would turn down a project like this as being impossible, and a waste of time to
attempt to build to a 1ms window. The hardware needs to be redesigned to interrupt when it
has data, or have an onboard timer, and it needs a device driver to handle the buffering.
Whoever designed the hardware needs a course in introductory hardware design for dummies.
One of the things I teach my students in my driver course is that you have to recognize
when the hardware is unrecoverable garbage and should be used for landfill. A device that
is designed to require 1ms application-based polling is landfill. Or possibly better
suited for a toxic waste disposal site.
A colleague of mine had to design to a 1ms polling of a device. It consumed 100% of the
CPU and effectively shut down the GUI and everything else in sight when it was running.
The system was unusable, even to interface to the program. His conclusion: this is a
fundamental mistake. to even try to design something to work with a piece of hardware so
incredibly poorly designed.
Using CAsyncSocket, there will be no need for a network thread., and no need for
synchronization of anything dealing with the network.
Largely the code I sketched above, and some copy-and-paste from a couple of my sample
applications, and the problem is solved.
joe
On Thu, 28 Oct 2004 12:19:01 -0700, Joan Coleman <JoanColeman@discussions.microsoft.com>
wrote:
>Hi Joe.
>
>I wanted to address a couple of issues from your last reply. I didn't
>really make myself clear on the data collection. I need one thread to
>collect data at 100ms (worker_thread1), but I also need a way to sample other
>instruments at up to 1ms. The instruments that require the 100ms data
>collection are polled. The other instruments (collected at 1ms) are read.
>That's why I need two worker threads. One to sit in the background polling
>and the other to run at 1ms (or as best it can, which is why I assume I need
>a callback). I moved the FKEY query into the windows message loop as you
>suggested. So here we go again...
>
>Start worker thread 1 (100ms polling)
>Detect FKEY1 keypress
> Start 1ms multimedia timer with callback
>
>Here's the 1 ms callback...
> Get A/D data and store in structure
> Get timestamp and store in structure
> Enter critical section
> Retrieve tracking data stored by worker_thread 1 and put into structure
> Exit critical section
> Move structure into buffer
> If buffer full
> start worker_thread 2 to write to disk
> update buffer pointer
> Endif
>End Callback
>
>worker_thread 1:
> polls network for tracking data
> Data received...
> enter critical section
> store data
> end critical section
> Go back to polling
>
>worker_thread 2:
> write buffer to disk
> indicate buffer written
>
>Will have an array of buffers (number unkown at present)
>
>BTW: The choice of FKEY1 is because the current system, which is unix
>based, uses it and the end user wants to keep it that way.
>When FKEY1 is again detected, data collection stops and the final buffer is
>written.
>
>"Joan Coleman" wrote:
>
>> OK. Will do. Thanks again for your help.
>>
>> "Joseph M. Newcomer" wrote:
>>
>> > Sure. The only problem in my case is that sometimes these things scroll off my attention
>> > span, so if you don't get a reply from me, send me private email reminding me to look at
>> > the newsgroup.
>> > joe
>> >
>> > On Fri, 22 Oct 2004 08:49:05 -0700, Joan Coleman <JoanColeman@discussions.microsoft.com>
>> > wrote:
>> >
>> > >Thank you both for taking the time to reply. I can see I need to do some more
>> > >homework! You are correct in assuming I was thinking procedurally - the
>> > >application is a port of our DAQ system from a UNIX based realtime operating
>> > >system, which can run up to a max of 1000 samples/sec. We realize that we
>> > >cannot expect the same performance out of a Windows operating system, but
>> > >hopefully can come close to meeting the expectations - thus my initial idea
>> > >of using the 1 ms callback. I have written a test program to test it, and
>> > >have seen that I do indeed get a one ms interrupt with a consistent glitch at
>> > >every 42 ms , which causes a 2 ms vs 1.
>> > >
>> > >The Windows system is not connected to any network, which should help as far
>> > >as system load is concerned; it will also be dedicated to this application
>> > >only.
>> > >
>> > >I will delete the callback, as you suggested, and try to digest all your
>> > >other comments and come up with a new design. Would it be ok to post this new
>> > >one and ask for comments again??? (I don't want to impose too badly), but I
>> > >am very grateful for being able to learn from your expertise. Thanks again
>> > >for all the help.
>> > >
>> > >"Joan Coleman" wrote:
>> > >
>> > >> Hi All
>> > >>
>> > >> I have not used multimedia timers nor threads in an MFC program as yet. I
>> > >> have searched through the newsgroups and done a lot of reading (incl. MVP
>> > >> sites - which are very helpful, thanks) and have come up with a design for my
>> > >> application. I am including the pseudo code and would really appreciate any
>> > >> suggestions regarding whether it will work. I do not want to start coding and
>> > >> find out I got something really stupid in here. If this is too much to ask,
>> > >> then perhaps some pointers as to what might be done better. TIA for all help.
>> > >>
>> > >> The task ( a "real time" data acquisition system) is as follows: at some
>> > >> point, the program (dialog) goes into a loop waiting on the user to interact
>> > >> via FKEYS; when the user enters FKEY1 the data collection process starts up;
>> > >> another FKEY1 press stops the data collection. (this can occur a lot of
>> > >> times). When the user is completely finished, via FKEY 8,the data acquisition
>> > >> part of the program is over .When data collection is not active, the user can
>> > >> interact via other function keys. When data collection is active, the user
>> > >> cannot interact, except by depressing FKEY 1 and by triggering an external
>> > >> event button.
>> > >>
>> > >> I am using VC 6.0 on a Windows XP Pro system - 3 GHz machine with dual
>> > >> processors. The program is dialog based; the dialog which is running this
>> > >> piece of code is a modal dialog started from the main dialog.
>> > >>
>> > >> Data Collection code:
>> > >>
>> > >> loop1 = loop2 = true;
>> > >> Start worker thread 1; //polls a network for tracking data and stores in
>> > >> dialog vars
>> > >> while (loop1)
>> > >> while(loop2)
>> > >> FKEY1 //start run
>> > >> setup multimedia timer (1 millisec) with callback function
>> > >> loop2 = false;
>> > >> endrun = false;
>> > >> FKEY2:
>> > >> FKEY3:
>> > >> .
>> > >> .
>> > >> FKEY8: //end DAQ
>> > >> loop2 = false;
>> > >> loop1 = false;
>> > >> stop worker thread 1;
>> > >> end loop2;
>> > >> update the dialog display; (via control variables)
>> > >> if endrun
>> > >> stop multimedia timer;
>> > >> loop2 = true;
>> > >> end if
>> > >> end loop1;
>> > >>
>> > >>
>> > >> the CALLBACK handler:
>> > >>
>> > >> CallBack
>> > >> fetch some instrument readings;
>> > >> build record containing A/D, tracking info, etc. and move to buffer
>> > >> check on event input;
>> > >> if FKEY 1 pressed
>> > >> write final buffer
>> > >> endrun = true
>> > >> else
>> > >> if buffer full
>> > >> start thread to write to disk;
>> > >> update buffering controls;
>> > >> end if
>> > >> end if
>> > >> end callback
>> > >>
>> > >> A couple assumptions/questions:
>> > >> endrun needs to be volatile
>> > >> the tracking data needs some sort of lock/unlock control governing its access.
>> > >> A specific question: how should other variables be declared so that the
>> > >> threads/callback procedure may access them - as private vars in the header
>> > >> section of the class?????
>> > >> Also, can I write to the disk (the file is a CFile object) in a thread, or
>> > >> should it be moved back in the loop1 code? (think I read somewwhere not to
>> > >> use MFC stuff in threads).
>> > >>
>> > >> Believe I also read somewhere that a 2 ms interrupt won't impact the system
>> > >> overhead too badly, but that 1ms will. Would it be better to put the
>> > >> callback code into a loop to do it twice, and use a 2 ms interrupt?
>> > >>
>> > >>
>> > >>
>> > >>
>> >
>> > Joseph M. Newcomer [MVP]
>> > email: newcomer@flounder.com
>> > Web: http://www.flounder.com
>> > MVP Tips: http://www.flounder.com/mvp_tips.htm
>> >
Joseph M. Newcomer [MVP]
email: newcomer@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
- Next message: Chris: "DECLARE_DYNCREATE and related stuff"
- Previous message: ak: "Re: default font in a MFC program (main menu)"
- In reply to: Joan Coleman: "Re: design Q : using timer/threads"
- Next in thread: Scott McPhillips [MVP]: "Re: design Q : using timer/threads"
- Messages sorted by: [ date ] [ thread ]