11

You have two threads, a and b. Thread a is in a forever loop, listening on a blocking socket 1. Thread b is also in a forever loop, listening on blocking socket 2. Both socket 1 and socket 2 may return data at arbitrary times, so Thread a may be sleeping forever waiting for data whereas Thread b constantly gets data from the socket and goes on with its processing. That's the background.

Now suppose they need to share a dictionary. When Thread a gets some data (if ever) it adds a key value pair into the dictionary after some processing, and then continues to wait for more data. When Thread b receives data from its socket it first queries the dictionary to see if there is information related to the data it has received before going on with its processing. There are no deletions to the dictionary, only inserts and queries (I'd be interested if this makes a difference in the end solution).

In a standard imperative language like python or c, this is pretty easy to do by making the dictionary available in both scopes and only querying it after a thread has acquired a lock, so Thread B always sees the most (well almost) up to date dictionary.

In Haskell, I seem to be struggling to come up with a good implementation of this pattern. MVars, can only have one item at a time so it can't be that Thread a puts in the dictionary, since a new update might occur and it would not be able to push that new dictionary until Thread b fetched it from the MVar. On the other hand if thread b uses an MVar to send a ready signal "ok!" to thread a, it may be the case that Thread a is sleeping on its read socket, so it would be unable to send back data until its read socket unblocked! There are also channels, but that seems messy since I would have to keep sending new dictionaries and Thread B would discard all but the last one.

The alternative solution that would work is to simply send the updates down a channel, and have thread B construct the dictionary for itself. However I'm wondering if there are better alternative solutions.

Thanks for taking the time to read this very long question!

Anil Vaitla
  • 2,958
  • 22
  • 31
  • 4
    I don't see the problem with `MVar`s. If A is waken up and gets new data, it tries to `takeMVar` the dictionary from the `MVar`. When it succeeds, update dictionary, `putMVar` dictionary, back to sleep. When B gets data, try to `takeMVar`, look up put back. Where does that break down? – Daniel Fischer Jan 02 '12 at 21:17
  • 1
    Thanks! Shoot Sorry that's exactly what I was looking for, nevermind. I'm a begineer still. I had thought it couldn't take out if it put in, and only another thread could. Stupid assumption in the back of my head! I'm just not sure how to close this question now? – Anil Vaitla Jan 02 '12 at 21:19
  • I can make it an answer that you can accept to mark the matter as resolved, or you can delete the question if you prefer (I don't know how, there should be a delete link somewhere, I believe). – Daniel Fischer Jan 02 '12 at 21:23
  • Seems @ehird is not the only one for keeping it. Answer below, I certainly wouldn't object to rep from upvotes or an accept ;) – Daniel Fischer Jan 02 '12 at 21:29

1 Answers1

10

You can use an MVar in the following way:

  • When thread A gets new data, it tries to get the dictionary with takeMVar. When that succeeds, it updates the dictionary and puts it back into the MVar
  • When thread B gets data, it tries to get the dictionary with takeMVar - in the above scenario where A seldom gets data that should succeed rather quickly on average. Then it does the lookup and puts the dictionary back.

As hammar pointed out, it's probably better to not directly use takeMVar and putMVar but rather wrap them in modifyMVar_ resp. modifyMVar to not leave the MVar empty if one thread gets an exception while using the dictionary.

In thread A, something like

modifyMVar_ mvar (\dict -> putMVar mvar (insert newStuff dict))

in thread B all you need is a simple readMVar (thanks to @hammar again for pointing that out).

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • For thread B, I think a `readMVar` would suffice (and possibly be more efficient), since the dictionary isn't being updated. – hammar Jan 03 '12 at 01:20
  • Probably. It's very unlikely that it gets hit by an exception between the take and the put, but not impossible. Then the `MVar` might end up empty. On the other hand it's quite possible that an exception would have to kill the entire process anyway. – Daniel Fischer Jan 03 '12 at 01:46
  • The implementation of `readMVar` uses `mask_`, so I don't think an exception _can_ happen between the take and the put. – hammar Jan 03 '12 at 01:59