No F*cking Idea

Common answer to everything

forkIO and Friends

| Comments

This post is sponsored by forkIO function and newChan in Haskell

Catz

What is this forkIO ?

forkIO is part of Control.Concurrent package and as it says it:

Sparks off a new thread to run the IO computation passed as the first argument, and returns the ThreadId of the newly created thread. The new thread will be a lightweight thread; if you want to use a foreign library that uses thread-local storage, use forkOS instead.

This is very neat if your program wants to use all the cores of your CPU or at least be more responsive not waiting for stuff to happen.

forkIO type is:

1
forkIO :: IO () -> IO ThreadId

Channels to help !

forkIO would be enough to start working on stuff but to make a real use of them we need a way of communicating with our threads. This actually opens design of our code to new stuff like building workers. There are other ways of communicating with threads like mVar but IMHO channels win hard.

Channels are part of Control.Concurrent.Chan package and are typable! Typed communication Yay!

Channels functions we need have the following type signatures:

1
2
3
newChan :: IO (Chan a)
writeChan :: Chan a -> a -> IO ()
readChan :: Chan a -> IO a

And that’s actually all we need. Let’s make some stuff working.

Ok So lets get this party started :)

I think most of the time it’s better to explain stuff on examples.

Just spawn!

First thing we want is just to spawn!

just_spawn.hs
1
2
3
4
5
6
import Control.Concurrent

main = do
  forkIO $ do
    putStrLn "Yay! i'm in thread!"
  putStrLn "I'm important i'm in Main thread!"

This is a very simple way to spawn a light weight thread via forkIO :>. As you can see it is a normal action so you can go dirty!

forkIO takes actions and give you back IO ThreadId so you can keep track / kill threads you don’t like

Just Spawn

Previous example was a bit cheating as it showed nothing really important so lets make some crazy threads printing stuff now.

crazygals.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Control.Concurrent

fanOfGarbage = do
  putStrLn "Garbage is best bad evar!"
  fanOfGarbage

fanOfClassicMusic = do
  putStrLn "Dude Garbage is garbage"
  fanOfClassicMusic

main = do
  putStrLn "hit it guys!"
  forkIO fanOfGarbage
  forkIO fanOfClassicMusic

Well compiling and running this gives you only “hit it guys” as main thread exits and child threads dies! lets fix it so we can say when they need to stop!:>

crazygals2.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Control.Concurrent

fanOfGarbage = do
  putStrLn "Garbage is best bad evar!"
  fanOfGarbage

fanOfClassicMusic = do
  putStrLn "Dude Garbage is garbage"
  fanOfClassicMusic

main = do
  putStrLn "hit it guys!"
  forkIO fanOfGarbage
  forkIO fanOfClassicMusic
  getLine
  putStrLn "Thank You Sir for stopping them!"

After launching it you can see how each thread is spamming its prints ;> So it work until you will hit enter. Cool so we have something working.

How does it work ? first of all we use forkIO to spawn threads and this time we have each “thread” function in separation. Each of them run forever like crazy music fans :). In this place we can make it simple by using forever from Control.Monad to make it simpler.

crazygals3.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Control.Concurrent
import Control.Monad (forever)

fanOfGarbage = do
  forever $ do
    putStrLn "Garbage is best bad evar!"

fanOfClassicMusic = do
  forever $ do
    putStrLn "Dude Garbage is garbage"

main = do
  putStrLn "hit it guys!"
  forkIO fanOfGarbage
  forkIO fanOfClassicMusic
  getLine
  putStrLn "Thank You Sir for stopping them!"

forever is part of Control.Monad as name says it is doing action forever ;) useful for stuff like workers or stuff that has to happen all the time it type is forever :: Monad m => m a -> m b.

Something useful, add channels

Cool so now we have some basics how to spawn a thread using forkIO but to have something that we actually can use in real life we need to have some sort of communication. I wanna present something i feel would be useful in almost every haskell program. Channel combined with forkIO.

If you programmed ever in Erlang or Go you will know what i’m talking about, channels are very similar to message passing. Basically it is a pipe that you can write to or read from in different threads/processes. This is one of the mechanism we can use to get data out of other threads. Because they are not sequential we can’t predict normal vals or time when they will be ready. One of the ways of getting response from threads are channels.

Channels are amazing because they are flexible :) And very natural. Basic principle is simple you write in one thread to the channel and read in other :)

But lets make an example that will show how powerful it is.

chanz.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Control.Concurrent
import Control.Monad (forever)
import Control.Concurrent.Chan

gossipGirl chan = do
  forever $ do
    gossip <- readChan chan
    putStrLn gossip

main :: IO ()
main = do
  putStrLn "Lets do some gossips"
  gossipChan <- newChan -- lets make new chan
  forkIO $ gossipGirl gossipChan -- spawn gossipGirl
  writeChan gossipChan "Garbage is garbage!"
  writeChan gossipChan "Garbage is garbage for reals!"
  getLine
  putStrLn "Thank You Sir for Info"

Nice! What happens here :) So new things are newChan that create channel which we will use to talk to our gossipGirl. readChan reads data from channel and writeChan writes stuff to channel. This is very simple :) So now lets generalize our worker into something that we can use in next mini tutorials. A worker.

A Worker

simple worker will take 1 channel as parameter and spawn thread this will help us in understanding how this whole thing works. (ofc if we don’t got it by now :) )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Control.Concurrent
import Control.Monad (forever)
import Control.Concurrent.Chan

worker chan foo = do
  forkIO $ forever $ foo chan

worker2 action = do
  forkIO $ forever action

gossipGirl chan = do
    gossip <- readChan chan
    putStrLn gossip

main :: IO ()
main = do
  putStrLn "Lets do some gossips"
  gossipChan <- newChan -- lets make new chan
  gossipChan2 <- newChan -- lets make new chan
  worker gossipChan gossipGirl -- spawn gossipGirl

  writeChan gossipChan "Garbage is garbage!"
  writeChan gossipChan "Garbage is garbage for reals!"

  worker2 (gossipGirl gossipChan2) -- woker2 2 girl!
  writeChan gossipChan2 "Umkay"
  writeChan gossipChan2 "Yez!"

  getLine
  putStrLn "Thank You Sir for Info"

Yes you can build workers as you want. I would not spend time on trying to build uber generic worker as it is usually custom and you don’t need to spend much time to make one :). Usually you can have worker types for particular tasks eg. databasesWriter, logWriters, counters etc.

Now why would you all this forkIO stuff ? here is there reason. Cat simulation!

cat.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Control.Concurrent
import Control.Monad (forever)
import Control.Concurrent.Chan

data AskForMeow = GibFood | Smile

meowMe chan chanBack = do
  niceTry <- readChan chan
  case niceTry of
    GibFood -> writeChan chanBack "Meow"
    Smile   -> writeChan chanBack "No"

cat action = do
  forkIO $ forever action


main :: IO ()
main = do
  putStrLn "Hey kitty kitty"

  foodInputChan <- newChan
  catOutputChan <- newChan

  cat $ meowMe foodInputChan catOutputChan

  writeChan foodInputChan Smile
  response <- readChan catOutputChan
  putStrLn response

  writeChan foodInputChan GibFood
  response' <- readChan catOutputChan
  putStrLn response'

  getLine
  return ()

Summary

I hope this gives a little insight into forkIO and channels functions as You should use them in your code. It is super simple to add them, they work miracles and i love them. Yes you don’t need to be expert on Kleisli arrows to use them ;).

Cheers!

Comments