【问题标题】:Locking an object during json.Marshal in Go在 Go 中的 json.Marshal 期间锁定对象
【发布时间】:2013-08-17 20:13:58
【问题描述】:

我想在将结构编组为 json 时将 RLock/RUnlock 添加到结构中。
下面的示例显示了我尝试做的事情。但是,它不起作用,因为每次调用json.Marshal,它都会运行Object.MarshalJSON方法,该方法本身调用json.Marshal,导致无限循环。

例子:

package main

import (
    "fmt"
    "encoding/json"
    "sync"
)

type Object struct {
    Name string
    Value int

    sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")

    return json.Marshal(o)
}

func main() {
    o := &Object{Name: "ANisus", Value: 42}

    j, err := json.Marshal(o)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}

Example in Playground

输出:

编组对象
编组对象
编组对象
...

显然,我可以在调用 json.Marshal 之前删除 MarshalJSON 方法并在主函数中调用 Lock()。但是,我的问题是:

有没有办法在结构的 MarshalJSON 方法中调用 json.Marshal(或者至少让 json 包处理编码)?

额外问题
为什么我的程序没有冻结?第二次递归调用 MarshalJSON 时,结构体不应该被锁定吗?

【问题讨论】:

  • 为什么要锁定对象?
  • @Akira:如果我有多个 goroutine 共享对象,我想防止其他例程在编码时更改数据。在我的示例中,我没有那个,但该示例并不是为了显示实际用法。
  • 取决于要编码的对象的大小:复制结构是一个有效的选项,恕我直言。
  • @Akira:复制结构不是原子操作。我仍然需要在复制期间锁定它以防止数据竞争。所以它仍然是同样的问题。
  • 真的。不过,我考虑time(copy(x)) < time(marshal(x)),因此锁定可能会更快。

标签: json go


【解决方案1】:

您可以为递归调用的类型设置别名。这里是Play

别名类型(JObject)没有定义marshal函数,所以它不会无限递归

package main

import (
    "fmt"
    "encoding/json"
    "sync"
)

type Object struct {
    Name string
    Value int

    sync.RWMutex
}

//Type alias for the recursive call
type JObject Object

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")
    // This works because JObject doesn't have a MarshalJSON function associated with it
    return json.Marshal(JObject(*o)) 
}

func main() {
    o := &Object{Name: "ANisus", Value: 42}

    j, err := json.Marshal(o)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}

【讨论】:

  • 完美。别名解决方案正是我试图实现的。它在嵌套结构中使用类型时有效,并且不需要对我的结构进行任何更改。谢谢!
  • 这个解决方案复制了一个不安全的互斥锁(去检查会引发错误)。
  • 嗯,而go vet 是正确的...复制互斥锁通常是不好的魔力。复制的互斥锁从未使用过,因此它实际上是安全的。也许这种使用模式不是兽医无法理解的。我想一个人可以制作两种类型 Object 和 type LockableObject struct { Object, sync.RWMutex } 。而 marshal 只编组 Object 部分,但 Lock 适用于整个可锁定的家伙
【解决方案2】:

简单回答: 您的程序由于无限递归而冻结。

您调用了json.Marshal(o),在您的方法中看起来像MarshalJSON(),但不幸的是,您还在MarshalJSON() 中调用了json.Marshal(o),这最终导致无限原因递归并耗尽系统内存

之所以称为common rookie mistake,是因为您的代码会导致无限递归。

这是使用String() 的代码的更简单版本

另一个递归示例:

package main

import "fmt"

type A int

func (a A) String() string {
    return fmt.Sprintf("%v", a)
}

func main() {
    var a A
    fmt.Println("this will never print", a)
}

这就是为什么 go 试图将 impose stack size limit 作为临时解决方案

2 个简单的解决方案

  • 使用其他名称
  • 不要返回return json.Marshal(o),而是返回项目

解决方案 1 示例

package main

import (
    "encoding/json"
    "fmt"
    "sync"
)

type Object struct {
    Name  string
    Value int

    sync.RWMutex
}

func (o *Object) ParseJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")

    return json.Marshal(o)
}

func main() {
    o := &Object{Name: "ANisus", Value: 42}

    j, err := o.ParseJSON() // THis would work
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)

    j, err = json.Marshal(o) // this would work
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}

Live Demo

解决方案 2 示例

主包

import (
    "encoding/json"
    "fmt"
    "sync"
)

type Item struct {
    Name  string
    Value int

}
type Object struct {
    item Item
    sync.RWMutex
}

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock()
    defer o.RUnlock()

    fmt.Println("Marshalling object")
    return json.Marshal(o.item)
}

func main() {
    o := &Object{item : Item{Name: "ANisus", Value: 42}}

    j, err := json.Marshal(o)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%s\n", j)
}

Live Demo

【讨论】:

  • 我选择了 David Budworth 解决方案,因为它最接近我所要求的解决方案。但是 +1 可以很好地解决递归问题。
猜你喜欢
  • 1970-01-01
  • 2012-05-30
  • 1970-01-01
  • 2020-04-23
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
相关资源
最近更新 更多