Learning about synchronization and state management in concurrent programming.
full lessons here👇:
https://programmerscareer.com/golang-basic-skill/
Generated by AI, there may be errors, for reference only
Topic: Atomic, Mutexes, Stateful Goroutines
To begin with, let’s set the context straight and understand these concepts:
1. Atomics:
Atomics in Go are part of the “sync/atomic” package. They provide low-level locking mechanisms used for synchronisation. For instance, these atomic functions can help to add, compare or load integers or pointers in a concurrent-safe way, ensuring that the operation is completed as a single uninterrupted task.
2. Mutexes:
Mutex stands for “Mutual Exclusion”. A mutex is like a token which can be held by a single goroutine at any point in time. If other goroutines try to acquire the mutex, they will be blocked until the goroutine that’s currently holding the mutex releases it. This is a critical tool for controlling access to a segment of code that cannot safely be executed concurrently.
3. Stateful Goroutines:
Stateful Goroutines are goroutines that maintain their own private state. Instead of sharing variables between goroutines and synchronising access using locks (like Atomics or Mutexes), each goroutine is responsible for managing its own state, and other goroutines interact with it by sending and receiving messages on channels.
Let’s take an example for better understanding:
Imagine we have multiple goroutines who all want to increment a counter. A traditional way to handle this situation would be to use a mutex to ensure safe access to the shared ‘counter’ variable.
1 | var ( |
In the above example, the worker goroutines lock the mutex to ensure exclusive access to the counter. Once the operation of incrementing the counter is finished, the worker unlocks the mutex.
Now, let’s see how we could re-implement this example using stateful goroutines:
1 | func main() { |
In this example, each increment request results in a message sent on the incrementer channel. The Stateful Goroutine receives these requests and handles them in isolation, ensuring the state (the counter) is safely incremented.
These are just for starter. Both Atomics and Mutexes have a much deeper level of complexity and use cases along with Stateful Goroutines.
Topic: Introduction to the ‘sync/atomic’ package
Go’s sync/atomic
package provides low-level atomic memory primitives useful for implementing synchronization algorithms.
Consider a scenario where multiple goroutines are reading and writing to a shared variable, this could lead to what’s called a data race, where the outcome depends on the sequence of execution.
Let’s understand this with a simple code example:
1 | package main |
In this program, we are starting two goroutines to increment the counter twice, once for each goroutine. The final value of the counter, however, is 2 and not 4. This is because of data race. To avoid this kind of scenario, we would use atomic functions. Here’s how to do it:
1 | package main |
By using sync/atomic
package, we ensure that only one goroutine plays with the counter at a time.
Topic: Introduction to Mutexes
Let’s consider a similar situation but with a map:
1 | package main |
This program too has a data race condition. The map
type in Go doesn’t support concurrent reads and writes. A Mutex is used to provide a locking mechanism to ensure that only one Goroutine is running the critical section of code at any point of time to prevent race condition from happening. Mutex is available in the sync
package and provides Lock and Unlock method:
1 | package main |
Now, the sharedRsc
can be safely accessed by multiple goroutines.
Topic: Using Mutexes to Protect Shared State
In this section, I’d like to ask you to try and create a short program which protects a shared state using a mutex. This could be a counter or any other variable which might be subject to a data race condition. This exercise should help consolidate your understanding of mutexes.
Bear in mind, these are the very basics of Atomics, Mutexes and Go Concurrency. We will soon touch upon the advanced aspects.
let’s take a further step and explore Stateful Goroutines.
Topic: Understanding Stateful Goroutines
As I briefly touched upon before, stateful goroutines are a different approach to dealing with shared state in concurrent code. The idea here is to encapsulate the state within a goroutine and only expose operations on that state through communication channels.
This approach can make it simpler to reason about the concurrency in your code because the consistent state is only altered in a single, welldefined, location.
Here’s an example:
1 | package main |
In this code the Counter
struct encapsulates an integer value and embeds a sync.Mutex to control access to this value. The Increment
and Read
methods on the Counter struct both use this mutex to control concurrent access to the value field.
Workers are represented by goroutines which receive jobs from a channel and increment the counter after each one. Because the state of the counter is completely encapsulated and only accessible through two methods, which are both safe for concurrent use, we know that our increment operation is atomic.
Topic: case study
Case Study 1: Basic Counter Server
Let’s take a look at an example where understanding of Atomics, Mutexes and Stateful Goroutines comes into picture. We are going to develop a basic counter server:
1 | package main |
In this example, we have a simple web server that increments a counter every time it gets a hit. We use a mutex to make sure that only one goroutine gets to increment the counter at a time. This way, we ensure that the counter always reflects the correct number of hits, even under heavy load.
Case Study 2: Cache Implemented with Mutex and Map
Consider you have a cache where you store key-value pairs and you’ve used Go’s map:
1 | type Cache struct { |
Here, you’re using a map to store your cache and Mutexes to make sure that only one goroutine at a time can access a value in the cache.
These case studies are designed to give you a basic understanding of how these sync primitives can be applied to solve real-world problems. As you can see, both Atomics and Mutexes have their own use cases. While Atomics are great for ensuring that only one goroutine can execute a particular section of code at a time, Mutexes bring a big savior to the tables where shared state is used and we want to avoid data race conditions.
I hope these case studies help you understand these concepts better. Can we move forward or do you have anything in mind that you would like to ask about?
Topic: Review and Assessments
To consolidate your understanding, let’s take a moment to review the key points covered.
- Atomic Variables: Go’s
sync/atomic
package provides low-level atomic memory primitives that can be used to implement synchronization algorithms and data structures in a multithreaded environment. - Mutexes: A Mutex is a mutual exclusion primitive that can be used to protect shared resources from concurrent access.
- Stateful Goroutines: Goroutines with a state encapsulated within themselves, only exposing operations on that state through communication channels.
Now, to truly grasp these concepts, let’s put your understanding to test. If you could, please verbally walk me through the following scenario:
Imagine you have to develop a server application which takes in numerical inputs from multiple clients simultaneously, and maintains a running total. Conceptually, what Go concurrency mechanism(s) would you utilize to ensure that the running total is always accurate, despite the simultaneous multiple client inputs?
Remember, there are no right or wrong answers and it’s perfectly fine if you don’t arrive at a solution immediately. The objective here is to apply your learning and understanding of the concepts!
中文文章: https://programmerscareer.com/zh-cn/go-basic-06/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: If you choose to repost or use this article, please cite the original source.
Comments