Process synchronization monitors in go

Angad Sharma
May 3 · 4 min read

Introduction

In the most recent times, programming has taken its fifth gear by leveraging process synchronization constructs to achieve thread level optimization. Popular languages like Java, python, support multi-threading. But control flow is often blurred in the process of achieving maximum concurrent throughput.

single threaded v/s multi-threaded processes

Semaphores

Semaphores are low level constructs which mainly have two methods defined on them. Wait() and Signal() . Semaphores make sure that the critical section of your code is atomic. Which means that in essence, shared memory cohesiveness should be sequential when two threads are trying to access it at the same time.

One thread acquires the lock, performs its critical section and then releases the lock for the other threads. In the meantime, all of the other threads are waiting in queue.

semaphores in action

Monitors

A Monitor is a high level process synchronization construct which abstracts away all of the timing information. It holds conditionals, shared memory, and timing information all under the same hood.

A Monitor class is an abstract data type which contains shared data variables and procedures. The variables are private and cannot be accessed from outside of the construct, only its procedures can access the variables. Only one thread can access a monitor class object at one time.

Monitor class

Monitors in go

We are going to construct a monitor interface which has all of the necessary functions required. Subsequently we are going to be creating a construct that satisfies the monitor interface and define the methods on it.

Create a file main.go

package mainimport (
"fmt"
"sync"
)
type Monitor interface {
Wait()
Signal()
GetData() []string
PutData(string)
}
type Words struct {
mutex *sync.Mutex
wordsArray []string
isInitialized bool
}
func (m *Words) Init() {
m.mutex = &sync.Mutex{}
m.wordsArray = []string{}
m.isInitialized = true
}

Here our Words struct satisfies the monitor interface. All of the member variables are private. Our task is to append words in an array atomically. Note that this is not an ideal use case for monitors but serves as a good example of the same.

Since we cannot access the member variables of the Words class we will be defining fetchers and getters on it.

  • GetData() []string : GetData function simple returns the whole array of words
  • PutData(string) : PutData function takes in a word as an argument and then atomically appends it to the array.

Now we are going to be defining the remaining functions

func (m *Words) Wait() {
if m.isInitialized {
m.mutex.Lock()
}
}
func (m *Words) Signal() {
if m.isInitialized {
m.mutex.Unlock()
}
}
func (m *Words) GetData() []string { return m.wordsArray }func (m *Words) PutData(word string) {
m.Wait()
// critical section
m.wordsArray = append(m.wordsArray, word)
// critical section done
m.Signal()
}

A monitor clubs together timing and control information. Here only initialized structs will be able to acquire the lock.

  • Wait() : The wait function acquires the mutex (mutual exclusion) lock if the member variables are defined
  • Signal() : The signal function releases the acquired lock so that the other threads can acquire it.

Our main function looks like this

func main() {
m := &Words{}
m.Init()
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
m.PutData("Angad")
}()
go func() {
defer wg.Done()
m.PutData("Sharma")
}()
wg.Wait()
fmt.Println(m.GetData())
}

Here we have initialized our Words struct and all of the member variables in it. Then we have started two goroutines to append words into the array. After the operation is done, we simply print out the whole array.

Output:

[Sharma Angad]

Application of monitors

  • Producer-consumer problem: One process produces data and the other process utilizes that data. Synchronization is required between the processes.
  • Dining-philosopher problem: K number of philosophers have chopsticks in front of them. They require 2 chopsticks to eat. They need to choose between thinking and eating according to their peers.
  • File read-write problem: Monitors can be used to prevent read-after-write, write-after-read, and write-after-write problems.

Conclusion

Monitors are useful data structures used to encapsulate all of the control information, timing information, and shared data under one roof. They are an abstraction over semaphores where we can define control statements over mutual exclusion.

Further reading

Angad Sharma

Written by

Aptly named L04DB4L4NC3R

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade