Go基础:了解 Atomic, Mutex, Stateful Goroutines in Go

学习并发编程中的同步和状态管理。

full lessons here👇:
https://programmerscareer.com/zh-cn/golang-basic-skill/

由AI生成,可能有错误,仅供参考

Topic: Atomic, Mutexes, Stateful Goroutines

首先,让我们设置上下文,理解这些概念:

1. Atomics:
Atomics 在 Go 中是“sync/atomic”包中的一个部分。它们提供了低级别的锁定机制,用来实现同步。例如,这些原子函数可以帮助在并发安全的情况下添加、比较或加载整数或指针。

2. Mutexes:
Mutex 是“Mutual Exclusion”的缩写。一个 Mutex 就像是一个 token,可以被单个 goroutine 持有。如果其他 goroutines 尝试获取 Mutex,他们将被阻塞,直到当前持有 Mutex 的 goroutine 释放它。这是一个控制访问某段代码的关键工具,这段代码不能安全地并发执行。

3. Stateful Goroutines:
Stateful Goroutines 是保持自己的私有状态的 goroutine。相反,不是通过锁定(像 Atomics 或 Mutexes)来共享变量之间的访问,而是每个 goroutine 负责管理自己的状态,其他 goroutines 通过在通道上发送和接收消息来交互。

让我们看一个例子:

假设我们有多个 goroutines 都想increment 一个计数器。传统的方法是使用 Mutex 来确保安全地访问共享的“counter”变量。

1
2
3
4
5
6
7
8
9
10
11
var (
mutex sync.Mutex
counter int
)

func worker() {
// Lock so only one goroutine at a time can access the counter
mutex.Lock()
counter++
mutex.Unlock()
}

现在,让我们看看如何使用 Stateful Goroutines 来实现这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
// Create a channel for sending increments requests to the goroutine
incrementer := make(chan bool)
// The stateful goroutine
go func() {
var counter int
for range incrementer {
counter++
}
}()
for i := 0; i < 10; i++ {
incrementer <- true
}
}

By using the 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"sync"
)

var (
sharedRsc = make(map[string]string)
)

func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
sharedRsc["one"] = "first value"
}()
go func() {
sharedRsc["two"] = "second value"
}()
wg.Wait()
fmt.Println(sharedRsc)
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"sync"
)

var (
mutex sync.Mutex
sharedRsc = make(map[string]string)
)

func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
mutex.Lock()
sharedRsc["one"] = "first value"
mutex.Unlock()
}()
go func() {
mutex.Lock()
sharedRsc["two"] = "second value"
mutex.Unlock()
}()
wg.Wait()
fmt.Println(sharedRsc)
}

Now, the sharedRsc can be safely accessed by multiple goroutines.

主题:使用 Mutex 保护共享状态

在这个部分,我想请你尝试创建一个短程序,使用 mutex 保护共享状态。这可能是一个计数器或任何其他变量,可能会受到数据竞争条件的影响。这个练习应该帮助巩固你的理解关于 mutex。

注意,这些是 Atomics、Mutexes 和 Go 并发编程的基本概念。我们很快就会涉及到高级方面。

让我们继续一步,探索 状态式 Goroutines

主题:了解状态式 Goroutines

正如我之前轻微地触摸过的那样,状态式 Goroutines 是一种不同的方法来处理共享状态在并发代码中。这个想法是将状态封装在一个 Goroutine 中,并且只通过通信通道暴露对该状态的操作。

这个方法可以使得你的代码更容易理解,因为一致的状态只有在单个、良好定义的位置被改变。

以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"fmt"
"math/rand"
"sync"
"time"
)

type Counter struct {
value int
o sync.Mutex
}

func (c *Counter) Increment() {
c.o.Lock()
c.value++
c.o.Unlock()
}

func (c *Counter) Read() int {
c.o.Lock()
defer c.o.Unlock()
return c.value
}

type Job struct {
delay time.Duration
name string
value int
}

func worker(id int, jobs <-chan Job, counter *Counter) {
for job := range jobs {
fmt.Println(job.name, "has arrived with value", job.value)
counter.Increment()
}
}

func main() {
rand.Seed(time.Now().UnixNano())
jobs := make(chan Job)

go func() {
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Millisecond)
jobs <- Job{time.Since(start), "Job "+strconv.Itoa(i), i}
}
}()

start := time.Now()
counter := &Counter{}
worker(1, jobs, counter)

fmt.Println("Final value:", counter.Read())
}

案例研究 1:使用 Mutex 保护共享状态

考虑你有一个缓存,用于存储键值对,你已经使用 Go 的 map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Cache struct {
cache map[string]string
mux sync.Mutex
}

func NewCache() *Cache {
return &Cache{
cache: make(map[string]string),
mux: sync.Mutex{},
}
}

func (c *Cache) Set(key string, value string) {
c.mux.Lock()
defer c.mux.Unlock()
c.cache[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
c.mux.Lock()
defer c.mux.Unlock()
val, found := c.cache[key]
return val, found
}

案例研究 2:使用 Mutex 和 Map 实现缓存

在这个案例中,你使用一个 map 存储键值对,并且使用 Mutex 保护该缓存。

回顾和评估

为了巩固你的理解,让我们回顾一下主要的概念:

  1. 原子变量:Go 的 sync/atomic 包提供了低级别的原子内存基本操作,可以用于实现同步算法和数据结构在多线程环境中。
  2. Mutex:一个 Mutex 是一种互斥 primitive,可以用于保护共享资源免受并发访问。
  3. 状态式 Goroutines:Goroutines 具有状态,封装在它们自身中,只暴露对该状态的操作通过通信通道。

现在,让我们将你的理解付诸实践。如果你能,请口头走我通过以下场景:

假设你需要开发一个服务器应用程序,它从多个客户端同时接收数字输入,并且维护一个累加总和。概念上,你将使用哪些 Go 并发机制来确保累加总和始终准确,尽管有多个客户端输入?

English post: https://programmerscareer.com/go-basic-06/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:本文为作者原创,转载请注明出处。

Go基础:理解错误处理: Errors, Panic, Defer, Recover in Go Go Basic:理解方法,接口,结构嵌入,泛型:涵盖Go中的OOP概念

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×