LAgent : an agent framework in F# – Part I – Workers and ParallelWorkers

-

Download frame­work here.

All posts are here:

Introduction

I like to try out dif­fer­ent pro­gram­ming par­a­digms. I started out as an ob­ject ori­ented pro­gram­mer. In uni­ver­sity, I used Prolog. I then learned func­tional pro­gram­ming. I also ex­per­i­mented with var­i­ous shared mem­ory par­al­lel par­a­digms (i.e. async, tasks and such). I now want to learn more about mes­sage based par­al­lel pro­gram­ming (Erlang style). I’m con­vinced that do­ing so makes me a bet­ter pro­gram­mer. Plus, I en­joy it …

My usual learn­ing style is to build a frame­work that repli­cates a par­tic­u­lar pro­gram­ming model and then write code us­ing it. In essence, I build a very con­strained en­vi­ron­ment. For ex­am­ple, when learn­ing func­tional pro­gram­ming, I did­n’t use any OO con­struct for a while even if my pro­gram­ming lan­guage sup­ports them.

In this case, I built my­self a lit­tle agent frame­work based on F# MailboxProcessors. I could have used MailboxProcessors di­rectly, but they are too flex­i­ble for my goal. Even to write a sim­ple one of these guys, you need to use async and re­cur­sion in a spe­cific pat­tern, which I al­ways for­get. Also, there are mul­ti­ple ways to to do Post. I wanted things to be as sim­ple as pos­si­ble. I was will­ing to sac­ri­fice flex­i­bil­ity for that.

Notice that there are se­ri­ous ef­forts in this space (as Axum). This is not one of them. It’s just a sim­ple thing I en­joy work­ing on be­tween one meet­ing and the next.

Workers and ParallelWorkers

The two ma­jor prim­i­tives are spawn­ing an agent and post­ing a mes­sage.

let echo = spawnWorker (fun msg -> printfn "%s" msg)
echo <-- "Hello guys!"

There are two kinds of agents in my sys­tem. A worker is an agent that does­n’t keep any state be­tween con­sec­u­tive mes­sages. It is a state­less guy. Notice that the lambda that you pass to cre­ate the agent is strongly typed (aka msg is of type string). Also no­tice that I over­loaded the <— op­er­a­tor to mean Post.

Given that a worker is state­less, you can cre­ate a whole bunch of them and, when a mes­sage is posted, route it to one of them trans­par­ently.

let parallelEcho = spawnParallelWorker(fun s -> printfn "%s" s) 10
parallelEcho <-- "Hello guys!”

For ex­am­ple, in the above code, 10 work­ers are cre­ated and, when a mes­sage is posted, it gets routed to one of them (using a su­per duper in­no­v­a­tive dis­patch­ing al­go­rithm I’ll de­scribe in the im­ple­men­ta­tion part). This par­al­lel­Worker guy is not re­ally needed, you could eas­ily built it out of the other prim­i­tives, but it is kind of cute.

To show the dif­fer­ence be­tween a worker and a par­al­lel­Worker, con­sider this:

let tprint s = printfn "%s running on thread %i" s Thread.CurrentThread.ManagedThreadId
let echo1 = spawnWorker (fun s -> tprint s)
let parallelEcho1 = spawnParallelWorker(fun s -> tprint s) 10
let messages = ["a";"b";"c";"d";"e";"f";"g";"h";"i";"l";"m";"n";"o";"p";"q";"r";"s";"t"]
messages |> Seq.iter (fun msg -> echo1 <-- msg)
messages |> Seq.iter (fun msg -> parallelEcho1 <-- msg)

 

The re­sult of the echo1 it­er­a­tion is:

**a run­ning on thread 11

b run­ning on thread 11

c run­ning on thread 11

d run­ning on thread 11

…**

While the re­sult of the par­al­l­elE­cho1 it­er­a­tion is:

**a run­ning on thread 13

c run­ning on thread 14

b run­ning on thread 12

o run­ning on thread 14

m run­ning on thread 13

…**

Notice how the lat­ter ex­e­cutes on mul­ti­ple threads (but not in or­der). Next time I’ll talk about agents, con­trol mes­sages and er­ror man­age­ment.

Tags

1 Comment

Comments