猿问

如何使变量线程安全

我是 Go 的新手,我需要使变量线程安全。我知道在 java 中你可以只使用关键字synchronized,但在 go 中似乎不存在这样的东西。有什么方法可以同步变量吗?



至尊宝的传说
浏览 82回答 1
1回答

白衣非少年

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.RWMutexinstead of sync.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 int32func 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)可以订阅接收消息/更新,并且代理能够向订阅的客户端广播消息。在一个有大量客户端随时可能订阅/取消订阅的系统中,并且可能需要随时广播消息,以安全的方式同步所有这些将很复杂。明智地使用通道,这个代理实现相当干净和简单。该实现对于并发使用是完全安全的,支持“无限”客户端,并且不使用单个互斥锁或共享变量,仅使用通道。
随时随地看视频慕课网APP

相关分类

Go
我要回答