This repository shows two approaches to handling concurrency in Golang:
- Single Process Lock using
sync.Mutex
- Distributed Lock using Redis Locks
A banking system processes account balance updates (debits and credits) from multiple concurrent transactions. The system ensures:
- Concurrency: Multiple transactions are processed simultaneously.
- Data Safety: Consistency of account balances using synchronization mechanisms like mutexes.
This approach uses Golang's sync.Mutex
to enforce mutual exclusion within a single process. It ensures that only one goroutine can access and modify shared resources (e.g., account balance) at a time.
- Use of Mutex: A
sync.Mutex
instance is used to lock critical sections. - Concurrency: Goroutines process transactions concurrently but safely.
- Example Use Case:
- Suitable for single-machine systems where all operations are within the same memory space.
- Simple and lightweight.
- No external dependencies.
- Very fast as operations are performed in memory.
- Cannot be used in distributed systems.
- Limited to processes running on a single machine.
- Each transaction is processed in a separate goroutine using the go keyword.
- A
sync.WaitGroup
ensures the main function waits for all transactions to complete. - The sync.Mutex ensures that only one goroutine can update an account's balance at a time.
// Account represents a bank account
type Account struct {
AccountNumber string
Balance float64
mu sync.Mutex // Mutex to protect the balance
}
var wg sync.WaitGroup
// Process transactions concurrently
for _, t := range transactions {
account, exists := accounts[t.AccountNumber]
if !exists {
fmt.Printf("Account %s not found\n", t.AccountNumber)
continue
}
wg.Add(1)
go account.ProcessTransaction(t.Amount, &wg)
}
// ProcessTransaction processes a single transaction on the account
func (a *Account) ProcessTransaction(amount float64, wg *sync.WaitGroup) {
defer wg.Done() // Notify when the goroutine is finished
// Lock the account to prevent race conditions
a.mu.Lock()
defer a.mu.Unlock()
if amount < 0 && a.Balance+amount < 0 {
fmt.Printf("Insufficient funds for account %s\n", a.AccountNumber)
return
}
a.Balance += amount
fmt.Printf("Processed transaction of %.2f on account %s. New balance: %.2f\n",
amount, a.AccountNumber, a.Balance)
}
Single Machine Locks Output:
Processed transaction of -200.00 on account 11111. New balance: 800.00
Processed transaction of 100.00 on account 11111. New balance: 900.00
Processed transaction of 300.00 on account 11111. New balance: 1200.00
Processed transaction of -500.00 on account 22222. New balance: 1500.00
Insufficient funds for account 22222
Final Account Balances:
Account 11111: 1200.00
Account 22222: 1500.00
This approach uses Redis to simulate distributed locking. Each critical section is protected by a distributed lock, ensuring data consistency across multiple processes or machines.
- Mock Redis: A mock Redis client is used for local testing.
- Distributed Lock: Implemented using Redis commands (
SETNX
,GET
,DEL
) to acquire and release locks. - Example Use Case:
- Ideal for distributed systems where multiple instances or processes need to synchronize access to shared resources.
- Supports distributed systems.
- Can synchronize processes across multiple machines.
- Scales well with cloud-native environments (e.g., Kubernetes).
- Requires a running Redis instance or equivalent service.
- Slightly slower due to network latency.
- More complex to implement and maintain.
- To make a simple ready-to-go process, I utilize mock for redis to simulate redis without run real Redis instance.
- Each transaction is processed in a separate goroutine using the go keyword.
- A
sync.WaitGroup
ensures the main function waits for all transactions to complete. - Redis SET with NX ensures that the operation is atomic, meaning the lock is created and has a TTL in a single operation.
- Simulates acquiring and releasing locks without connecting to a real Redis server.
// MockRedis simulates a Redis client with basic lock functionality
type MockRedis struct {
data map[string]string
mu sync.Mutex
}
// RedisLock represents a distributed lock
type RedisLock struct {
client *MockRedis
key string
value string
}
// AcquireLock tries to acquire the lock
func (lock *RedisLock) AcquireLock(ttl time.Duration) (bool, error) {
return lock.client.SetNX(lock.key, lock.value, ttl)
}
// ReleaseLock releases the lock
func (lock *RedisLock) ReleaseLock() error {
val, err := lock.client.Get(lock.key)
if err != nil {
return fmt.Errorf("lock not found")
}
// Ensure the lock is released by the process that acquired it
if val == lock.value {
_, err = lock.client.Del(lock.key)
if err != nil {
return err
}
}
return nil
}
Distributed Locks Output:
Processed transaction of 100.00 on account 11111. New balance: 1100.00
Insufficient funds for account 22222. Transaction skipped.
Processed transaction of 300.00 on account 11111. New balance: 1400.00
Processed transaction of -500.00 on account 22222. New balance: 1500.00
Processed transaction of -200.00 on account 11111. New balance: 1200.00
Final Account Balances:
Account 11111: 1200.00
Account 22222: 1500.00
Feature | sync.Mutex |
Redis Locks |
---|---|---|
Scope | Single process | Distributed processes |
Performance | Very fast (in-memory) | Slightly slower (network I/O overhead) |
Complexity | Simple | Medium |
Dependencies | None | Redis or equivalent |
Use Case | Single-machine systems | Multi-machine or distributed systems |
Failure Handling | Handled within process | Requires TTL and retries |
- Your application runs on a single machine.
- You don’t need to synchronize across multiple processes or machines.
- Simplicity and performance are priorities.
- Your application runs in a distributed system.
- You need to synchronize access across multiple processes or machines.
- Scalability and cross-instance consistency are required.
-
Single Process Lock (Mutex)
- Clone the repository.
- Navigate to the
single
folder. - Run the program:
go run main.go
-
Distributed Lock (Redis)
- Ensure Redis is running or use the mock Redis provided.
- Navigate to the
distributed\mock_redis
folder. - Run the program:
go run main.go
This project is licensed under the MIT License.