Simple Implementation of the Monitor Object Pattern in Go for Easy Concurrency Control

Photo by Alesia Kozik: https://www.pexels.com/photo/black-screen-with-graph-6781273/

Introduction

Sometimes in a multi-threaded program, you need to protect a resource from concurrent access, that is access by multiple threads. One way of doing this, is to implement a Monitor Object.

To achieve this we do the following:

  1. We identify the resource we need to protect from being accessed by multiple threads
  2. Then we create a Monitor object in which we encapsulate the shared resource, and we provide method for access and modifying it.
  3. We can use Go’s Mutex struct to protect our resource, ensuring that only one thread at a time can access it.
  4. We must also implement methods that lock the mutex before accessing the shared resource, and unlock it afterwards.

Implementation in Go

In this example we will implement a simple program to administer stocks, or in our case exactly one stock.

The Stock struct

This is how we define the Stock struct:

type Stock struct {
	Name  string
	Price float64
}

func NewStock(name string, price float64) *Stock {
	return &Stock{
		Name:  name,
		Price: price,
	}
}

A Stock in our example just has a name and a price. The only method we define on this struct is a simple constructor.

Next we come to the Monitor object:

type Monitor struct {
	Value     *Stock
	Mutex     sync.Mutex
	Condition *sync.Cond
}

func NewMonitor(stock *Stock) *Monitor {
	return &Monitor{
		Value:     stock,
		Mutex:     sync.Mutex{},
		Condition: sync.NewCond(&sync.Mutex{}),
	}
}

func (m *Monitor) UpdatePrice(price float64) {
	m.Mutex.Lock()
	defer m.Mutex.Unlock()
	fmt.Printf("Updating price of %v to %v for stock %v\n", m.Value.Name, price, m.Value.Name)
	m.Value.Price = price
	m.Condition.Broadcast()
}

func (m *Monitor) WaitForRelease() {
	limit := 120.0
	m.Mutex.Lock()
	for m.Value.Price < limit {
		fmt.Printf("Waiting for price of %v to go over %v\n", m.Value.Name, limit)
		m.Condition.Wait()
	}
	fmt.Printf("Price of %v is now %v and above %v\n", m.Value.Name, m.Value.Price, limit)
	m.Mutex.Unlock()
}

A short description:

In the Monitor struct we hold:

  1. A reference to the shared resource, in our case a Stock struct.
  2. A Mutex is used to make sure only one thread at a time can access our shared resource
  3. We use a Cond struct to be able to signal other threads we have finished with the resource.

In the NewMonitor we create a new Monitor object with the supplied Stock struct as it value.

In UpdatePrice() we do the following:

  1. We lock the Mutex and defer unlocking it.
  2. Next we update the price
  3. Finally we signal any waiting thread that we have finished.

In WaitForRelease() we wait for the price to go over a certain limit, till that limit is reached the thread is blocked. Once the stock reaches the limit, the thread continues executing.

Testing time

Let’s test our simple setup:

func main() {
	monitor := NewMonitor(NewStock("GOOG", 110.0))
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			monitor.UpdatePrice(110.0 + float64(i)*2.0)
		}()
	}

	go monitor.WaitForRelease()
	wg.Wait()
	fmt.Printf("Stock is now %+v\n", monitor.Value)
}

This is what happens in main:

  1. We create a new Monitor object with a NewStock struct as its value.
  2. Next we start ten go routines, each of which will try to update the price.
  3. After this we wait for the price to go over 120.
  4. Finally we print out the final price of the stock.

Conclusion

Since Go was built for multi-threaded applications, writing this pattern was quite easy and it turned out to be quite straightforward. Especially using the Cond to signal between go routines make life quite easy.

One caveat: this is a simple example. In a later article I will build a more elaborate example where I will also address the possiblity of data-races.

Another enhancement would be to make the Monitor struct more generic.

Leave a Reply

Your email address will not be published. Required fields are marked *