为了账号安全,请及时绑定邮箱和手机立即绑定

如何使变量线程安全

如何使变量线程安全

Go
至尊宝的传说 2023-04-24 16:25:06
我是 Go 的新手,我需要使变量线程安全。我知道在 java 中你可以只使用关键字synchronized,但在 go 中似乎不存在这样的东西。有什么方法可以同步变量吗?
查看完整描述

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


查看完整回答
反对 回复 2023-04-24
  • 1 回答
  • 0 关注
  • 81 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信