Understanding the Fan In Concurrency Pattern in Golang

Kacper Bąk
7 min readDec 9, 2022

--

Photo by Pawel Czerwinski on Unsplash

Concurrency in Go is a programming technique that allows multiple tasks to be executed simultaneously. It enables programs to run faster and more efficiently by taking advantage of multiple cores and processors. Go provides a number of built-in concurrency primitives, such as goroutines and channels, that make it easy to write concurrent programs. The Fan In, Fan Out pattern is a popular concurrency pattern in Go that can be used to improve the performance of your applications.

To get started with concurrency programming in Go, you should first become familiar with the language’s built-in concurrency primitives, such as goroutines and channels. Once you understand how these primitives work, you can begin to explore the various concurrency patterns available in Go, such as the Fan In, Fan Out pattern. Finally, you should practice writing concurrent programs in Go to gain a better understanding of how to use the language’s concurrency features.

Goroutines are lightweight threads of execution which can be used to create highly concurrent applications. They are designed to allow programs to run multiple tasks concurrently and to efficiently manage resources. Goroutines are mainly used for dividing tasks up into smaller, concurrent tasks that can be run in parallel. A goroutine is created when a function is called and can be paused and resumed at any time.

The primary benefit of goroutine-based design patterns is their ability to manage resources more efficiently. As goroutines can be created and paused at any time, they allow for dynamic resource management, allowing more efficient usage of memory and processing power.

Goroutine-based design patterns can also be used to simplify program logic, allowing developers to write code that is easier to understand and maintain. They can also help to improve the performance of applications by reducing the number of active threads and optimizing the use of resources.

In addition, goroutine-based design patterns can be used to create more robust applications. By running tasks in parallel, developers can reduce the risk of a single point of failure and make applications more resilient to errors and crashes.

Different types of Goroutines

  1. Worker goroutines: These are the most commonly used goroutines. They are used to perform a specific task in the background.
  2. Event goroutines: These goroutines are used to wait for a specific event to occur and then execute a callback.
  3. Pub/Sub goroutines: These goroutines are used to publish and subscribe to events.
  4. Timer goroutines: These goroutines are used to schedule tasks to be executed at a specific time.
  5. System goroutines: These goroutines are used to execute system calls.
  6. WaitGroup goroutines: These goroutines are used to wait for a group of goroutines to finish executing before proceeding with the next operation.

Steps to create a concurrent program using Goroutines

  1. Identify tasks that can be broken down into smaller, concurrent tasks.
  2. Create a goroutine for each task and pass the necessary arguments to each goroutine.
  3. Wait for all goroutines to finish before continuing.
  4. Use channels to communicate between goroutines and to pass data between them.
  5. Handle any errors that occur in goroutines.
  6. Close any goroutines when done to free up resources.

Benefits of Goroutines

  1. Increased efficiency: Goroutines allow multiple processes to be run simultaneously, allowing for more efficient use of computer resources.
  2. Reduced latency: By running multiple processes in parallel, Goroutines can help reduce latency when accessing data or performing computations.
  3. Easier to code: Goroutines provide a simpler and more intuitive way to code concurrent processes, making it easier for developers to create programs that run in parallel.
  4. Reduced memory usage: Goroutines are lightweight, meaning that they require less memory than other types of concurrent processes. This can result in significant memory savings for programs that use Goroutines.
  5. Improved scalability: Goroutines are designed to scale easily, allowing programs to easily scale up or down depending on the demand.

Disadvantages of Goroutines

  1. Goroutines can consume more memory than traditional threads, thus requiring more resources to manage.
  2. Goroutines have a high risk of deadlock due to their single-threaded nature.
  3. Managing and debugging Goroutines can be difficult due to their asynchronous nature.
  4. Goroutines are not suitable for CPU-bound tasks as they are not able to take advantage of multiple cores.
  5. Goroutines can be difficult to scale and may not perform as well as other concurrency models.

Deadlock

Deadlock is a situation in which two or more competing actions are each waiting for the other to finish, and thus neither ever does. Deadlocks can occur in a variety of situations, such as when two computer programs share the same resource, and each program tries to acquire exclusive access to the resource while the other one is still using it.

Race Conditions

Race conditions occur when two or more goroutines access the same variable concurrently and at least one of the goroutines is writing to that variable. This can lead to unexpected behavior, as the values of the variable could change unexpectedly due to the concurrent operations.

Applications that could be developed based on Goroutines

  1. Web Crawlers: Goroutines can be used to build efficient web crawlers that can scale to crawl large websites.
  2. Networking Applications: Goroutines can be used to build efficient networking applications such as web servers, proxies and streaming services.
  3. Concurrency Primitives: Goroutines can be used to build efficient concurrency primitives such as semaphores and locks.
  4. Parallel Computing: Goroutines can be used to perform parallel computing tasks such as data mining and machine learning.
  5. Distributed Systems: Goroutines can be used to build efficient distributed systems such as distributed databases, distributed caches, and distributed computing clusters.
  6. Asynchronous Programming: Goroutines can be used to build efficient asynchronous programs that can handle multiple tasks concurrently.

Fan In Pattern

The Fan In pattern is an architectural pattern used in Golang to help manage parallelism and concurrency. This pattern allows a single function to receive multiple inputs, process them in parallel then fan out the results to multiple functions for further processing. This pattern is used to optimize complex tasks by breaking them down into smaller chunks which can be processed in parallel, reducing the overall completion time.

Example of Fan In in Golang:

func FanIn(
done <-chan interface{},
channels ...<-chan interface{},
) <-chan interface{} {
var wg sync.WaitGroup
multiplexedStream := make(chan interface{})

multiplex := func(c <-chan interface{}) {
defer wg.Done()
for l := range c {
select {
case <-done:
return
case multiplexedStream <- l:

}
}

}

wg.Add(len(channels))
for _, c := range channels {
go multiplex(c)
}

go func() {
wg.Wait()
close(multiplexedStream)
}()

return multiplexedStream
}

This code works by creating a channel that multiple inputs can write to. The channel is monitored by a select loop, which will read from the channel whenever new data is available. The select loop will then call a function to process the data from each input. The function will read from the input and then write the result back to the channel. Once all inputs have been processed, the loop will end and the program will exit.

Multiplexed stream in fan in pattern is a technique used in distributed systems to allow a single process to receive and process multiple streams of data concurrently. The fan in pattern allows multiple streams of data to be combined into a single multiplexed stream, which can then be processed as a single entity. This technique is especially useful when dealing with data that is coming from multiple sources, such as distributed services.

This pattern is used when a single function reads from multiple inputs and proceeds until all are closed. This is made possible by multiplexing the input into a single channel.

The pattern is commonly used in concurrent programming when multiple threads or processes need to communicate with each other and coordinate their activities. The pattern is also used in network programming such as when a network server needs to process multiple simultaneous requests from its clients.

You can check for yourself how the whole thing has been written, or alternatively test for yourself how the pattern behaves. Link to the repository: https://github.com/53jk1/fanin

Experiment

I did a little experiment, using this design pattern, and creating an operation that takes 1ms for each send.

 sleep := func() interface{} { time.Sleep(time.Millisecond); return nil }
 // Fan in Fan out pattern
for i := 0; i < numGenerators; i++ {
generators[i] = repeatfn.RepeatFn(done, sleep)
}

With a simple script like this, I will be able to see how effective Goroutines are and how much faster they will speed up the execution of operations, thanks to concurrency.

I also created a simple approach, using only a for loop, to see what the discrepancy in results would be per unit time.

 now := time.Now()
for i := 0; i < config.JobsToBeDone; i++ {
sleep()
}

Result

Number of CPUs:  24
Number of Jobs to be done: 1000000
Number of Jobs to be done per Goroutine: 41666
GOMAXPROCS: 24
Function took 2.168893321s

Comparision

Number of CPUs:  24
Number of Jobs to be done: 100
Number of Jobs to be done per Goroutine: 4
GOMAXPROCS: 24
With Fan In pattern function took 11.667374ms
Without Fan In pattern function took 105.523594ms
Number of CPUs:  24
Number of Jobs to be done: 1000
Number of Jobs to be done per Goroutine: 41
GOMAXPROCS: 24
With Fan In pattern function took 13.231147ms
Without Fan In pattern function took 1.053818546s
Number of CPUs:  24
Number of Jobs to be done: 10000
Number of Jobs to be done per Goroutine: 416
GOMAXPROCS: 24
With Fan In pattern function took 32.424669ms
Without Fan In pattern function took 10.543847834s
Number of CPUs:  24
Number of Jobs to be done: 100000
Number of Jobs to be done per Goroutine: 4166
GOMAXPROCS: 24
With Fan In pattern function took 228.212241ms
Without Fan In pattern function took 1m45.38254762s

Conclusions

The Fan-In pattern is a useful design pattern that can help to improve the performance of an application’s architecture by allowing multiple sources of data to be accessed concurrently and collated into one single object. It is especially useful when there are large volumes of data that need to be collected from different sources in order to build a larger, more complex object.

--

--

Kacper Bąk
Kacper Bąk

Written by Kacper Bąk

Software Engineer & Backend Developer

No responses yet