On Wed, Jan 27, 2010 at 7:31 AM, Brian Denheyer <briand@aracnet.com> wrote:
On Tue, 26 Jan 2010 22:41:44 -0800
Thomas DuBuisson <thomas.dubuisson@gmail.com> wrote:

> Brian Denheyer <briand@aracnet.com> wrote:
>
> > On Tue, 26 Jan 2010 10:54:03 -0800
> > Thomas DuBuisson <thomas.dubuisson@gmail.com> wrote:
> >
> > > > doEvent f usDelay = forkIO $
> > > >   threadDelay usDelay
> > > >   doEvent f usDelay
> > > >   f
> >
> > Are you sure that's right ? It seems to be a memory-gobbling
> > infinite loop...
> >
>
> Infinite loop?  yes, that is what you wanted.  Memory gobbling?  Why
> would you think that? Are you assuming no TCO and a full stack push
> on every function call?  Haskell compilers don't work that way.

Why would I think that ?
I think that because the following code:

import Control.Concurrent

f = putStrLn "foo"

doEvent f usDelay = do forkIO $ threadDelay usDelay
                      doEvent f usDelay
                      f

_really_ _does_ start to consume all of the memory on my system, that's
why.  I don't know why, but that's what it does on my system.  It's not
obvious to me that it should do that.  So maybe ghci is not doing TCO.

That would be a bug!  I'm using GHC 6.12.1 i386 and both interpreted using GHCi CLI and compiled (even without optimization) there is no memory growth using either of the two versions.  If you are using the latest GHC then consider filing a report at haskell.org/ghc



> 2) It strikes me as funny you suspect the first way when there is zero
> fundamental difference between that and the way you posted except
> that: a) My version maintains the correct delay.
> b) My version forks the doEvent call and runs the action in the older
> thread while yours forks the action thread and keeps the doEvent in
> the older thread.  I suppose keeping the doEvent as the old thread is
> good so you can kill it with the original ThreadID that would be
> returned to the caller.
>

Thanks for the explanation, as I said I'm a little fuzzy on what
constitutes a thread, so the two versions will help.

Well Concurrent Haskell has a version of green threads [1] which are scheduled (via the GHC RTS) on some number of OS threads (typically either 1 or equal to the number of cores on the machine).  These light weight "green threads" are extremely cheap to create and destroy, costing a matter of 1K or less per thread, benchmarks can create/destory 100k threads in seconds.  "forkIO" creates green threads while "forkOS" (which you generally should not use) creates OS threads.

One interesting thing I noticed in the docs (which is not important
for what I am trying to do, just interesting):

There is no guarantee that the thread will be rescheduled promptly when
the delay has expired, but the thread will never continue to run
earlier than specified.

Right, and this is the same as any commodity OS - you can delay for a certain amount of time and once the delay is up you will get rescheduled but the computer is likely busy with other processes/interrupts etc at the exact microsecond you are done.  I think the GHC RTS schedules threads in 50us slices by default.  Also, GHC uses (or used to use) allocation as a scheduling point so if you have long-running tight loops that don't allocate (and don't explicitly call 'yield') then this could be quite long. In practice I've only once had a problem (GTK GUI didn't update during a long computation - still not sure this was the reason).

Cheers,
Thomas

[1] http://en.wikipedia.org/wiki/Green_threads