1 回答
TA贡献1155条经验 获得超0个赞
synchronized
在 Java 中是一种只允许单个线程执行代码块的方法(在任何给定时间)。
在 Go 中有许多结构可以实现这一点(例如互斥锁、通道、等待组、原语sync/atomic
),但 Go 的格言是:“不要通过共享内存进行通信;相反,通过通信共享内存。”
因此,与其锁定和共享变量,不如尝试不这样做,而是在 goroutine 之间传递结果,例如使用通道(这样您就不必访问共享内存)。
当然,在某些情况下,最简单、直接的解决方案是使用互斥锁来保护多个 goroutine 对变量的并发访问。在这种情况下,您可以这样做:
var (
mu sync.Mutex
protectMe int
)
func getMe() int {
mu.Lock()
me := protectMe
mu.Unlock()
return me
}
func setMe(me int) {
mu.Lock()
protectMe = me
mu.Unlock()
}
上述解决方案可以在几个方面进行改进:
使用
sync.RWMutex
instead ofsync.Mutex
,以便getMe()
可以锁定为只读,这样多个并发读者就不会互相阻塞。在(成功)锁定之后,建议使用 解锁
defer
,这样如果后续代码中发生错误(例如运行时恐慌),互斥体仍将被解锁,避免资源泄漏和死锁。尽管此示例非常简单,但不会发生任何不良情况,也不能保证无条件地使用延迟解锁。最好让互斥体靠近它应该保护的数据。因此,“包装”
protectMe
及其mu
在结构中是个好主意。而且如果我们这样做,我们也可以使用嵌入,因此锁定/解锁变得更加方便(除非必须不公开此功能)。
因此,上述示例的改进版本可能如下所示(在Go Playground上尝试):
type Me struct {
sync.RWMutex
me int
}
func (m *Me) Get() int {
m.RLock()
defer m.RUnlock()
return m.me
}
func (m *Me) Set(me int) {
m.Lock()
m.me = me
m.Unlock()
}
var me = &Me{}
func main() {
me.Set(2)
fmt.Println(me.Get())
}
此解决方案还有另一个优点:如果您需要 的多个值Me,它会自动为每个值具有不同的、单独的互斥锁(我们的初始解决方案需要为每个新值手动创建单独的互斥锁)。
虽然这个例子是正确有效的,但可能不实用。因为保护单个整数并不真正需要互斥体。我们可以使用sync/atomic包实现相同的目的:
var protectMe int32
func getMe() int32 {
return atomic.LoadInt32(&protectMe)
}
func setMe(me int32) {
atomic.StoreInt32(&protectMe, me)
}
这个解决方案更短、更干净、更快。如果您的目标只是保护单个值,则首选此解决方案。如果您应该保护的数据结构更复杂,atomic
甚至可能不可行,那么使用互斥量可能是合理的。
现在在展示了共享/保护变量的例子之后,我们还应该给出一个例子,我们应该实现什么目标来实现“不要通过共享内存进行通信;相反,通过通信共享内存”。
情况是你有多个并发的 goroutines,你使用一个变量来存储一些状态。一个 goroutine 更改(设置)状态,另一个 goroutine 读取(获取)状态。要从多个 goroutine 访问此状态,必须同步访问。
这个想法是不要有这样的“共享”变量,而是一个 goroutine 设置的状态,它应该“发送”它,而另一个 goroutine 会读取它,它应该是状态“发送到”(或者换句话说,另一个 goroutine 应该接收更改后的状态)。所以没有共享状态变量,取而代之的是2 个 goroutines 之间的通信。Go 为这种“协程间”通信提供了出色的支持:channels。对通道的支持内置于语言中,有send statements,receive operators和其他支持(例如你可以循环在通道上发送的值)。
让我们看一个实际/现实生活中的例子:“经纪人”。代理是一个实体,其中“客户端”(goroutines)可以订阅接收消息/更新,并且代理能够向订阅的客户端广播消息。在一个有大量客户端随时可能订阅/取消订阅的系统中,并且可能需要随时广播消息,以安全的方式同步所有这些将很复杂。明智地使用通道,这个代理实现相当干净和简单。该实现对于并发使用是完全安全的,支持“无限”客户端,并且不使用单个互斥锁或共享变量,仅使用通道。
- 1 回答
- 0 关注
- 81 浏览
添加回答
举报