In Go, locking manages concurrent access to shared resources, while buffered channels enable non-blocking communication up to a set capacity.
Wed, Jun 11, 2025
3 min read
Locks are used to prevent two or more processes from using the same resources or enter a critical section, which helps in concurrency to maintain a consistent state in a distributed environment. It also helps to prevent deadlocks or race conditions.
In Go , sync.Mutex
is used as a mutual exclusion lock, which allows only one goroutine to access a piece of code or data(resources) at a time.
Let’s import sync and define a type:
package main
import (
"fmt"
"sync"
)
type syncedBuffer struct {
lock sync.Mutex
}
Meanwhile buffer is used like a dynamic, resizable array of bytes which helps to store bytes or strings. It helps in efficient memory management while handling byte streams or constructing strings. It dynamically allocates memory for what you store and also may reserve extra capacity to avoid frequent resizing.
We can import it as bytes and also define it in the type:
package main
import (
"bytes"
"fmt"
"sync"
)
type syncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
Let’s define the main function and the variables s of struct syncedBuffer and wg(waitgroup) :
func main() {
var s syncedBuffer
var wg sync.WaitGroup
wg.Add(2)
//logic code here
wg.Wait()
}
wg is a waitgroup variable that runs asynchronously, which prevents the main function from completing before the goroutines. Here wg.Add(2)
adds two goroutines in the waitgroup and wg.wait()
waits until these goroutines are processed before moving further.
Now let’s add the go routines:
package main
import (
"bytes"
"fmt"
"sync"
"time"
)
type syncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
// locks and buffer in Go
func main() {
var s syncedBuffer
var wg sync.WaitGroup
wg.Add(2)
// first goRoutine: holds lock longer
go func() {
defer wg.Done()
s.lock.Lock()
fmt.Println("[goRoutien 1] acquired lock , writing")
s.buffer.WriteString("Hello from goRoutine 1!\n")
time.Sleep(5 * time.Second) // Simulate long operation
s.lock.Unlock()
fmt.Println("[goRoutien 1] released lock")
}()
// delay second goRoutine slightly so it hits the lock while the first is holding the lock
go func() {
defer wg.Done()
time.Sleep(1000 * time.Millisecond)
fmt.Println("[goRoutien 2] trying to acquire lock...")
s.lock.Lock()
fmt.Println("[goRoutine 2] acquired lock , writing...")
s.buffer.WriteString("hello from go routine 2 \n")
time.Sleep(2 * time.Second)
s.lock.Unlock()
fmt.Println("[go routine 2 ] released lock")
}()
wg.Wait()
time.Sleep(3 * time.Second)
fmt.Println("final buffer content: \n" + s.buffer.String())
}
[goRoutien 1] acquired lock , writing
[goRoutien 2] trying to acquire lock...
[goRoutien 1] released lock
[goRoutine 2] acquired lock , writing...
[go routine 2 ] released lock
final buffer content:
Hello from goRoutine 1!
hello from go routine 2
Thanks for reading till the end. The minions are proud of you! 😊