# Building a Worker Pool in F# with MailboxProcessor

## Introduction

While studying concurrent network programming, I explored how to implement a worker pool in F# using `MailboxProcessor`, F#'s built-in actor abstraction.

The example launches multiple workers that process port numbers concurrently. A `CountdownEvent` is used to wait until all work items have been completed before shutting down the workers gracefully.

This approach is conceptually similar to Go's goroutines and channels but follows a more actor-oriented design.

* * *

## Core Components

The implementation consists of three main pieces:

| Component | Responsibility |
| --- | --- |
| MailboxProcessor | Worker actor |
| Message Type | Communication protocol |
| CountdownEvent | Completion tracking |

* * *

## Defining Messages

Workers communicate through strongly typed messages.

```fsharp
type Message =
    | Port of int
    | Stop
```

This provides a clear protocol:

*   `Port` → process a port number
    
*   `Stop` → terminate the worker
    

Unlike string-based messaging, the compiler verifies message correctness.

* * *

## Creating a Worker

Each worker runs as an independent actor.

```fsharp
let worker (id: int) =
    MailboxProcessor.Start(fun inbox ->
        let rec loop () = async {
            let! msg = inbox.Receive()

            match msg with
            | Port p ->
                printfn "Worker %d processed port %d" id p
                return! loop ()

            | Stop ->
                ()
        }
        loop ()
    )
```

Important characteristics:

*   Sequential message processing
    
*   No explicit locking
    
*   Independent execution context
    
*   Message-driven behavior
    

* * *

## Tracking Completion

To emulate Go's `WaitGroup`, the example uses `CountdownEvent`.

```fsharp
let completed = CountdownEvent(1024)
```

Each processed port decrements the counter.

```fsharp
completed.Signal() |> ignore
```

The main thread blocks until all tasks are finished.

```fsharp
completed.Wait()
```

* * *

## Creating the Worker Pool

A pool of 100 workers is created.

```fsharp
let workers =
    [|
        for i in 1 .. 100 ->
            worker i
    |]
```

Each worker owns its mailbox and processes incoming messages independently.

* * *

## Dispatching Work

Port numbers are distributed across workers.

```fsharp
for port in 1 .. 1024 do
    let index = port % 100
    workers.[index].Post(Port port)
```

This creates a simple round-robin scheduling strategy.

* * *

## Graceful Shutdown

After all work has completed, a termination message is sent.

```fsharp
for w in workers do
    w.Post(Stop)
```

This avoids abruptly terminating active workers.

* * *

## Go vs F# Concurrency

The design maps naturally to concepts familiar to Go developers.

| Go | F# |
| --- | --- |
| Goroutine | MailboxProcessor |
| Channel | Mailbox |
| WaitGroup | CountdownEvent |
| Send Message | Post |
| Close Channel | Stop Message |

Although the implementation style differs, both approaches rely on message passing rather than shared-state synchronization.

* * *

## Why MailboxProcessor?

`MailboxProcessor` offers several advantages:

*   Actor-style concurrency
    
*   Strongly typed messages
    
*   No manual lock management
    
*   Clear separation of worker responsibilities
    
*   Scales well for network and backend services
    

This makes it a useful building block for:

*   Port scanners
    
*   TCP servers
    
*   Telemetry systems
    
*   Chat applications
    
*   Distributed services
    
*   IoT backends
    

* * *

## Conclusion

F#'s `MailboxProcessor` provides a concise and powerful way to implement concurrent worker pools. By combining actors with typed messages and synchronization primitives such as `CountdownEvent`, I can build systems that are both scalable and easier to reason about than traditional shared-memory approaches.
