Clinton
Clinton

Reputation: 23135

Haskell: Atomic IO wrapper/laziness?

I've wrote the following function that I believe should perform IO atomically (as long as everyone else is using the same MVar).

atomicIO :: MVar () -> IO a -> IO a
atomicIO mvar io =
  do
    takeMVar mvar
    result <- io
    putMVar mvar ()
    return result

Also, from what I understand, some parts of Haskell IO are lazy (e.g.. IORefs), so there is no need to actually perform the action in this section. It can just return a 'thunk' (is that the right word?) which lists the tasks that needs to be done.

The idea is that, the critical section (i.e. the single threaded part) should be quite quick.

However, lets say I'm writing to IORefs, and I want Haskell to start computing the result immediately, so it's ready to do when required. But like I said before, I don't want to get locked in the critical section when we're holding the MVar lock.

So I've thought about doing this:

    result <- io `par` io

or this

    return result `par` result

or this

    result `par` return result

But I'm not sure if this does the job. Is one of these the right approach, or is there another way? (my concern about the latter two is IO () actions, as I figure evaluating () in parallel doesn't actually do any work).

Upvotes: 4

Views: 450

Answers (2)

dave4420
dave4420

Reputation: 47052

Where you have

writeIORef myRef newValue

Replace that with

newValue `par` writeIORef myRef newValue

That will spark off a thread to evaluate newValue in the background...

...with the caveat that it will only evaluate it to WHNF (basically, only the first level of the data structure will be evaluated). So an Int would be entirely evaluated, but a String wouldn't. A Maybe a value would be entirely evaluated to Nothing or partially evaluated to Just _.

So if you are using a more complex data structure, you will need to use force from Control.DeepSeq in the deepseq package, which will completely evaluate the value.

force newValue `par` writeIORef myRef newValue

If you want to use modifyIORef, I don't think you can reuse modifyIORef directly, but you can certainly define a similar function (modifyIORef is defined in terms of readIORef and writeIORef anyway):

modifyIORefInParallel :: NFData a => IORef a -> (a -> a) -> IO ()
modifyIORefInParallel ref f
   = do old <- readIORef ref
        let new = f old
        force new `par` writeIORef ref new

(Note that if the last line was force (f old) `par` writeIORef ref (f old) it wouldn't work: the value that was forced in parallel would not be the value that was stored in the ref.)

(The NFData a restriction is from using force.)

Upvotes: 4

Peter
Peter

Reputation: 1693

The only way to get fast critical sections is by limiting yourself to fast IO actions. I don't see how trying to force strict evaluation inside atomicIO would give you any speed up. Moreover, note that atomicIO itself might not be strictly evaluated, in which case the strict evaluation inside atomicIO doesn't have any effect.

In any case, you can use seq instead of par for strict evaluation, as you're not trying to spark parallel computations.

Upvotes: 1

Related Questions