Re: design Q : using timer/threads
From: Joan Coleman (JoanColeman_at_discussions.microsoft.com)
Date: 10/31/04
- Next message: GAK: "Loadframe() fails when no menu is created"
- Previous message: Doug Harrison [MVP]: "Re: Newbie: CDialog: How to get the Enterkey work the same way as TAB?"
- Messages sorted by: [ date ] [ thread ]
Date: Sun, 31 Oct 2004 09:59:03 -0800
Thank you for your response. Looks like we need to do some more homework. OK
to post back again???
"Joseph M. Newcomer" wrote:
> 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: GAK: "Loadframe() fails when no menu is created"
- Previous message: Doug Harrison [MVP]: "Re: Newbie: CDialog: How to get the Enterkey work the same way as TAB?"
- Messages sorted by: [ date ] [ thread ]