0

0

Go语言中带有互斥锁的结构体如何安全地实现JSON序列化

霞舞

霞舞

发布时间:2025-10-24 12:09:01

|

606人浏览过

|

来源于php中文网

原创

Go语言中带有互斥锁的结构体如何安全地实现JSON序列化

本文探讨了在go语言中,当结构体包含`sync.rwmutex`并自定义`marshaljson`方法时,如何避免因内部递归调用`json.marshal`而导致的无限循环问题。核心解决方案是利用类型别名来创建一个不带自定义序列化方法的副本,从而在确保数据并发安全的同时,实现结构体的正确json编码

引言

在Go语言开发中,我们经常需要将结构体序列化为JSON格式。当结构体包含共享数据且在并发环境中被访问时,为了保证数据的一致性和完整性,通常会引入像sync.RWMutex这样的互斥锁。为了在JSON序列化过程中也保证数据访问的线程安全,我们可能会尝试自定义MarshalJSON方法,并在其中加锁。然而,这种做法如果不当,很容易导致无限递归的问题。

问题描述:自定义MarshalJSON的陷阱

考虑一个包含读写互斥锁的结构体Object,我们希望在将其序列化为JSON时,获取一个读锁以防止数据在序列化过程中被修改。一个直观但错误的实现方式可能如下所示:

package main

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

type Object struct {
    Name  string
    Value int
    sync.RWMutex // 嵌入读写互斥锁
}

// 错误的MarshalJSON实现
func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock() // 获取读锁
    defer o.RUnlock() // 确保释放读锁

    fmt.Println("Marshalling object")
    // 错误:在此处直接调用 json.Marshal(o) 会导致无限递归
    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)
}

运行上述代码,你会发现程序会输出大量的 "Marshalling object" 消息,最终导致溢出(stack overflow)错误。这是因为当json.Marshal(o)被调用时,json包会检查o是否实现了MarshalJSON方法。由于Object结构体确实实现了该方法,json.Marshal会再次调用o.MarshalJSON()。这个过程无限循环,直到程序崩溃。

为什么程序没有立即冻结?

立即学习go语言免费学习笔记(深入)”;

你可能会好奇,为什么多次调用o.RLock()没有导致程序冻结或死锁。这是因为sync.RWMutex的RLock()方法允许多个读者同时持有读锁。因此,即使在递归调用中多次尝试获取读锁,只要没有写锁被持有,这些读锁都能成功获取,从而避免了死锁。然而,这并不能阻止无限递归本身,最终仍会导致资源耗尽。

解决方案:利用类型别名打破递归

解决这个问题的关键在于,在MarshalJSON方法内部调用json.Marshal时,需要避免再次触发当前结构体的MarshalJSON方法。我们可以通过类型别名(Type Alias)来实现这一点。

DeepL Write
DeepL Write

DeepL推出的AI驱动的写作助手,在几秒钟内完善你的写作

下载

类型别名会创建一个与原类型底层结构相同但不继承原类型方法集的新类型。这意味着,如果我们创建一个Object的类型别名,并对该别名实例调用json.Marshal,json包将不会发现该别名类型实现了MarshalJSON方法,而是会使用其默认的反射机制进行序列化,从而打破递归。

以下是修正后的MarshalJSON实现:

package main

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

type Object struct {
    Name  string
    Value int
    sync.RWMutex
}

// 定义一个类型别名,它不包含Object的MarshalJSON方法
type JObject Object 

func (o *Object) MarshalJSON() ([]byte, error) {
    o.RLock() // 获取读锁
    defer o.RUnlock() // 确保释放读锁

    fmt.Println("Marshalling object")
    // 将 *o 转换为 JObject 类型,然后对其进行 JSON 序列化
    // JObject 没有 MarshalJSON 方法,因此会使用默认序列化机制
    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)
}

运行这段代码,你会看到正确的JSON输出:

Marshalling object
{"Name":"ANisus","Value":42}

程序只输出了一次 "Marshalling object",表明MarshalJSON方法只被调用了一次,且成功地完成了序列化。

工作原理:

  1. 当json.Marshal(o)被调用时,它会识别出Object类型实现了MarshalJSON方法,并调用o.MarshalJSON()。
  2. 在o.MarshalJSON()内部,首先获取读锁,确保数据在序列化期间不被修改。
  3. 然后,JObject(*o)将当前的Object实例o的值复制并转换为JObject类型的一个新值。
  4. json.Marshal(JObject(*o))被调用。由于JObject是一个类型别名,它没有继承Object的MarshalJSON方法,因此json包会对其进行标准的反射序列化,而不会再次调用MarshalJSON(),从而避免了递归。
  5. 序列化完成后,读锁被释放。

注意事项与总结

  • 锁的粒度:在MarshalJSON中加锁是一种确保序列化时数据一致性的有效方式。然而,锁的粒度需要根据实际需求仔细考虑。如果结构体内部有更复杂的嵌套结构,可能需要在更细粒度上进行锁控制。
  • 性能考量:频繁的加锁和解锁操作会带来一定的性能开销。如果你的应用对序列化性能有极高要求,并且数据一致性可以通过其他方式(如在数据写入时保证不变性)来保障,可以考虑是否需要在MarshalJSON中加锁。
  • 其他序列化器:这种类型别名的模式不仅适用于encoding/json,也适用于其他需要自定义序列化行为但又需避免递归的Go标准库或第三方库,例如encoding/gob等。
  • 指针接收者与值接收者:本例中使用的是指针接收者*Object,这在修改结构体或处理大型结构体时是常见的做法。类型别名通常用于值类型,JObject(*o)会创建一个o的副本。如果你的结构体非常大,创建副本可能会有性能开销,但对于大多数场景来说,这是可接受的。

通过巧妙地使用类型别名,我们可以在Go语言中安全、高效地为带有互斥锁的结构体实现自定义JSON序列化,既保证了并发安全,又避免了无限递归的陷阱。这种模式是Go语言中处理自定义序列化和并发问题的强大工具

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

418

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

76

2025.09.10

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

220

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

192

2025.07.04

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

395

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.5万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号