Golang 异步对象序列化Map并发冲突和解决方法

简介: #冲突过程 异步对象序列化,对象里面如果有map,并且使用json.marshal to string,或者其他方式对map 执行了read、write操作。 而业务逻辑同步写对象map值,那么,就会触发: fatal error: concurrent map read and map write 本质原因在于:两个goroute对同一个map执行了读写并发,而golang map默认是不

冲突过程

异步对象序列化,对象里面如果有map,并且使用json.marshal to string,或者其他方式对map 执行了read、write操作。
而业务逻辑同步写对象map值,那么,就会触发: fatal error: concurrent map read and map write
本质原因在于:两个goroute对同一个map执行了读写并发,而golang map默认是不支持并发操作、没有加锁

冲突解决

(1)在主业务逻辑里面,提前把对象转为string,丢给异步任务去执行(不局限写log,可以是其他对map的操作)

(2)map 的读写加锁,而读写加锁对性能的影响,以及死锁等又带来新的麻烦。
关于优化并发map的可以参考这个分析,比较全面:https://misfra.me/optimizing-concurrent-map-access-in-go/

(3)主、异步任务交互参数改为string,或者对象的深clone,也就是完整的copy对象值,重造对象,而不是直接引用指针。

一个冲突实例的源码

读写map的行,都加以注释说明了。
补充说明:
(1)json.marshal 一个对象,如果对象里面有map,那么会触发对map的read操作

(2)seelog 在配置里面配置的是异步log,也就是写log的时候,加入异步队列,而代码里面也是传入对象的,从而这个对象是异步序列化,也就是异步map read操作

package main

import (
    "encoding/json"
    "fmt"
    seelog "github.com/cihub/seelog"
    "sync"
    "time"
)

//https://github.com/cihub/seelog/wiki/Logger-types
/* file seelog-main.xml
<seelog>  
    <outputs formatid="main">  
        <buffered size="10" flushperiod="1000">  
            <rollingfile type="date" filename="gologs/main.log" datepattern="2006.01.02" maxrolls="30"/>  
        </buffered>  
    </outputs>  
    <formats>  
        <format id="main" format="%Msg%n"/>  
    </formats>  
</seelog>  
*/

func main() {
    var logger, _ = seelog.LoggerFromConfigAsFile("conf/seelog-main.xml")
    seelog.ReplaceLogger(logger)
    defer seelog.Flush()
    seelog.Info("需要输入的日志")

    for i := 0; i < 40; i++ {
        //i := 0
        go workerSimlutor(i)
    }
    fmt.Printf("40 worker simlutor running....")
    time.Sleep(time.Duration(3) * 60 * time.Second)
    fmt.Printf("40 worker simlutor finish....")
}

func workerSimlutor(i int) {
    bmap := make(map[string]string)
    name := fmt.Sprintf("text_%d", i)

    ei := EventInfo{
        Name:           name,
        BusinessKey:    name,
        BusinessParams: bmap,
        lockBizParams:  &sync.RWMutex{},
    }

    for id := 0; id < 100; id++ {
        //strOut := ei.String() // 这里提前主动string,那么异步log 里面不会有map的read操作。
        seelog.Infof("i:%d,ei:%v", i, ei)// 这里是对象传入log,log里面 异步,异步marshal 对象,触发对对象里面map的read操作
    }

    for id := 0; id < 100; id++ {
        ei.SetVariable("num", fmt.Sprintf("%d", i)) // 这里更新map,触发write操作,
    }
}

type EventInfo struct {
    // 注册的 handler 的名称
    Name           string            `json:"name"`
    BusinessKey    string            `json:"businessKey"`
    BusinessParams map[string]string `json:"businessParams"`
    lockBizParams  *sync.RWMutex     `json:"-"`
}

func (ei *EventInfo) String() string {
    //ei.lockBizParams.Lock()
    //defer ei.lockBizParams.Unlock()  // 这里加锁能解决问题,不过复杂场景下,会导致死锁
    if b, err := json.Marshal(ei); err != nil {
        return fmt.Sprintf("%s", ei)
    } else {
        return fmt.Sprintf("%s", b)
    }
}

func (ei *EventInfo) SetVariable(key, val string) {
    ei.BusinessParams[key] = val
}

func (ei *EventInfo) GetVariable(key string) string {
    val, ok := ei.BusinessParams[key]
    if !ok {
        return ""
    }
    return val
}

冲突错误日志前半部分

40 worker simlutor running....fatal error: concurrent map read and map write

goroutine 8 [running]:
runtime.throw(0x64f48e, 0x21)

D:/AmiddleStability/Go1.7/go/src/runtime/panic.go:566 +0x9c fp=0xc0420276f0 sp=0xc0420276d0

runtime.mapaccess2(0x6079e0, 0xc042054f90, 0xc04213a540, 0xc0421608a0, 0xc04213a540)

D:/AmiddleStability/Go1.7/go/src/runtime/hashmap.go:340 +0x250 fp=0xc042027738 sp=0xc0420276f0

reflect.mapaccess(0x6079e0, 0xc042054f90, 0xc04213a540, 0xc042054f90)

D:/AmiddleStability/Go1.7/go/src/runtime/hashmap.go:1008 +0x46 fp=0xc042027770 sp=0xc042027738

reflect.Value.MapIndex(0x6079e0, 0xc042139f40, 0x95, 0x5f7080, 0xc04213a540, 0x98, 0x8, 0x200, 0xc042027870)

D:/AmiddleStability/Go1.7/go/src/reflect/value.go:1040 +0x12f fp=0xc0420277f8 sp=0xc042027770

fmt.(*pp).printValue(0xc04213c000, 0x6079e0, 0xc042139f40, 0x95, 0x76, 0x1)

D:/AmiddleStability/Go1.7/go/src/fmt/print.go:738 +0x1216 fp=0xc0420279e8 sp=0xc0420277f8

fmt.(*pp).printValue(0xc04213c000, 0x626640, 0xc042139f20, 0x99, 0x76, 0x0)

D:/AmiddleStability/Go1.7/go/src/fmt/print.go:764 +0x23a2 fp=0xc042027bd8 sp=0xc0420279e8

fmt.(*pp).printArg(0xc04213c000, 0x626640, 0xc042139f20, 0x76)

D:/AmiddleStability/Go1.7/go/src/fmt/print.go:668 +0x1fc fp=0xc042027cd0 sp=0xc042027bd8

fmt.(*pp).doPrintf(0xc04213c000, 0x6487a5, 0xa, 0xc04213e620, 0x2, 0x2)

D:/AmiddleStability/Go1.7/go/src/fmt/print.go:985 +0x1244 fp=0xc042027db8 sp=0xc042027cd0

fmt.Sprintf(0x6487a5, 0xa, 0xc04213e620, 0x2, 0x2, 0xc042027e58, 0x0)

D:/AmiddleStability/Go1.7/go/src/fmt/print.go:196 +0x71 fp=0xc042027e10 sp=0xc042027db8

github.com/cihub/seelog.(*logFormattedMessage).String(0xc042139f50, 0x664c02, 0x73b100)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/logger.go:369 +0x59 fp=0xc042027e58 sp=0xc042027e10

github.com/cihub/seelog.(*commonLogger).processLogMsg(0xc0420963f0, 0x61ef02, 0x736d00, 0xc042139f50, 0x73b100, 0xc042162a10)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/logger.go:312 +0xa3 fp=0xc042027ea0 sp=0xc042027e58

github.com/cihub/seelog.(*asyncLogger).processQueueElement(0xc0420963f0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclogger.go:115 +0x11f fp=0xc042027f40 sp=0xc042027ea0

github.com/cihub/seelog.(*asyncLoopLogger).processItem(0xc0420963f0, 0x0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:57 +0xf6 fp=0xc042027f68 sp=0xc042027f40

github.com/cihub/seelog.(*asyncLoopLogger).processQueue(0xc0420963f0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:63 +0x4b fp=0xc042027f88 sp=0xc042027f68

runtime.goexit()

D:/AmiddleStability/Go1.7/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc042027f90 sp=0xc042027f88

created by github.com/cihub/seelog.NewAsyncLoopLogger

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:40 +0xa6

goroutine 1 [sleep]:
time.Sleep(0x29e8d60800)

D:/AmiddleStability/Go1.7/go/src/runtime/time.go:59 +0xef

main.main()

D:/AmiddleStability/godemo/test/rolllogseelog_concurrent_map.go:24 +0x18f

goroutine 5 [semacquire]:
sync.runtime_notifyListWait(0xc04200abd0, 0x0)

D:/AmiddleStability/Go1.7/go/src/runtime/sema.go:267 +0x130

sync.(*Cond).Wait(0xc04200abc0)

D:/AmiddleStability/Go1.7/go/src/sync/cond.go:57 +0x87

github.com/cihub/seelog.(*asyncLoopLogger).processItem(0xc0420961b0, 0x0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:50 +0xba

github.com/cihub/seelog.(*asyncLoopLogger).processQueue(0xc0420961b0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:63 +0x4b

created by github.com/cihub/seelog.NewAsyncLoopLogger

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:40 +0xa6

goroutine 6 [semacquire]:
sync.runtime_notifyListWait(0xc04200add0, 0x0)

D:/AmiddleStability/Go1.7/go/src/runtime/sema.go:267 +0x130

sync.(*Cond).Wait(0xc04200adc0)

D:/AmiddleStability/Go1.7/go/src/sync/cond.go:57 +0x87

github.com/cihub/seelog.(*asyncLoopLogger).processItem(0xc0420962d0, 0x0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:50 +0xba

github.com/cihub/seelog.(*asyncLoopLogger).processQueue(0xc0420962d0)

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:63 +0x4b

created by github.com/cihub/seelog.NewAsyncLoopLogger

D:/AmiddleStability/Go1.8/gopath/src/github.com/cihub/seelog/behavior_asynclooplogger.go:40 +0xa6
目录
相关文章
|
缓存 安全 PHP
PHP中的魔术方法与对象序列化
本文将深入探讨PHP中的魔术方法,特别是与对象序列化和反序列化相关的__sleep()和__wakeup()方法。通过实例解析,帮助读者理解如何在实际应用中有效利用这些魔术方法,提高开发效率和代码质量。
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
582 4
|
JSON Go 数据格式
Golang 对象深拷贝的方案以及性能比较
文章探讨了在Go语言中实现对象深拷贝的三种方法:使用gob序列化、JSON序列化和定制化拷贝,并对比了它们在性能和代码复杂度方面的优劣,建议根据具体需求选择最适合的方法。
631 1
|
JSON 人工智能 Go
在Golang中序列化JSON字符串的教程
在Golang中,使用`json.Marshal()`可将数据结构序列化为JSON格式。若直接对JSON字符串进行序列化,会因转义字符导致错误。解决方案包括使用`[]byte`或`json.RawMessage()`来避免双引号被转义,从而正确实现JSON的序列化与反序列化。
725 7
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
ES6中map对象的使用,确实比Object好使哈
ES6中Map对象的使用优势,包括任意类型作为键、直接获取大小、增删查改操作等。Map的键可以是函数、对象、NaN等,支持forEach循环和for...of循环。
275 1
ES6中map对象的使用,确实比Object好使哈
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
存储 安全 Java
Map的并发处理,助你提升编程效率,代码更优雅高效。
【10月更文挑战第19天】Map使用技巧大公开:从选择合适的Map实现(如HashMap、TreeMap、LinkedHashMap)到利用Map的初始化、使用Map.Entry遍历、运用computeIfAbsent和computeIfPresent方法,再到Map的并发处理,助你提升编程效率,代码更优雅高效。
322 2
|
JSON 前端开发 数据格式
前端的全栈之路Meteor篇(五):自定义对象序列化的EJSON介绍 - 跨设备的对象传输
EJSON是Meteor框架中扩展了标准JSON的库,支持更多数据类型如`Date`、`Binary`等。它提供了序列化和反序列化功能,使客户端和服务器之间的复杂数据传输更加便捷高效。EJSON还支持自定义对象的定义和传输,通过`EJSON.addType`注册自定义类型,确保数据在两端无缝传递。
420 1

推荐镜像

更多