Go lang Interview Experience with American Express - Final Round
In my final round interview with American Express for the role of Senior Go Lang Developer on May 12, one of the key questions asked was about implementing a custom waitSync
package in Go. This package is similar to the sync.WaitGroup
provided by the Go standard library, and it serves as a synchronization mechanism to wait for a collection of goroutines to finish executing.
In this blog, I'll walk you through the implementation of this custom waitSync
package and demonstrate its usage with a simple example.
The waitSync
package provides a WaitGroup
struct that allows you to manage the synchronization of multiple goroutines. Here’s how to implement it without sync.Cond
:
waitsync/waitsync.go
package waitsync
import "sync"
// WaitGroup is a custom implementation of a synchronization primitive.
type WaitGroup struct {
counter int
mutex sync.Mutex
}
// NewWaitGroup initializes and returns a new WaitGroup.
func NewWaitGroup() *WaitGroup {
wg := &WaitGroup{}
return wg
}
// Add increments the WaitGroup counter by the specified delta.
func (wg *WaitGroup) Add(delta int) {
wg.mutex.Lock()
wg.counter += delta
wg.mutex.Unlock()
}
// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
wg.Add(-1)
}
// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
wg.mutex.Lock()
defer wg.mutex.Unlock()
for wg.counter > 0 {
wg.mutex.Unlock()
time.Sleep(10 * time.Millisecond) // Busy-wait with sleep
wg.mutex.Lock()
}
}
Explanation
- WaitGroup Struct: The
WaitGroup
struct contains a counter to keep track of the number of active goroutines and a mutex to ensure thread-safe access to the counter. - NewWaitGroup Function: This function initializes a new
WaitGroup
instance. - Add Method: This method increments or decrements the counter by the specified delta.
- Done Method: This method is a convenience function that decrements the counter by one.
- Wait Method: This method blocks until the counter becomes zero, indicating that all goroutines have completed. It uses a busy-wait loop with a sleep interval to periodically check the counter.
To illustrate how to use the custom waitSync
package, let's create a simple program that spawns multiple goroutines, each simulating some work by sleeping for a different duration before completing.
main.go
package main
import (
"fmt"
"time"
"waitsync"
)
func main() {
var wg = waitsync.NewWaitGroup()
for i := 1; i <= 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
time.Sleep(time.Second * time.Duration(i))
fmt.Printf("Goroutine %d finished\n", i)
}(i)
}
wg.Wait()
fmt.Println("All goroutines finished")
}
Explanation
- Create WaitGroup: Initialize a new
WaitGroup
instance. - Add Goroutines: Spawn five goroutines, each sleeping for a different duration before printing a message.
- Wait for Completion: Use the
Wait
method to block until all goroutines have finished executing.
When you run this program, you'll see each goroutine print a message after its respective sleep duration, followed by a final message indicating that all goroutines have completed.
Conclusion
Implementing a custom waitSync
package in Go is a great way to deepen your understanding of synchronization mechanisms. This exercise was particularly useful in preparing for my final round interview with American Express, where such practical knowledge of Go's concurrency patterns was crucial.
By creating your own WaitGroup
implementation without using sync.Cond
, you can appreciate the inner workings of Go's synchronization primitives and enhance your skills as a Go developer. I hope this blog has provided a clear and practical guide to implementing and using a custom waitSync
package in Go.