当前位置:首页 > 文章列表 > Golang > Go问答 > 如何跨 goroutine 共享地图

如何跨 goroutine 共享地图

来源:stackoverflow 2024-04-18 17:09:34 0浏览 收藏

各位小伙伴们,大家好呀!看看今天我又给各位带来了什么文章?本文标题《如何跨 goroutine 共享地图》,很明显是关于Golang的文章哈哈哈,其中内容主要会涉及到等等,如果能帮到你,觉得很不错的话,欢迎各位多多点评和分享!

问题内容

我正在尝试在 go 中编写一个通知结构,该结构将保存一系列键及其各自的值,并且如果值低于阈值,则会触发通知。

当第一个样本低于阈值时,通知应仅触发一次,并且低于该阈值的其他样本不应再次触发,直到该值上升到阈值以上。

例如,假设我的阈值是 10,我发送 15、14、11、10、... 9 的样本。一旦发送 9,就应该触发通知。进一步的样品 8、7、4 不应造成任何影响。以下示例如 5、6、7、9、10、11、14、30 不应执行任何操作。一旦样本再次低于 10:30、20、15、10、7...,则必须发送另一个通知。

当多个 goroutine 操作我的结构时,我遇到了问题。

我尝试使用sync.mutex进行同步,并且还使用了sync.map,但没有运气。我感觉某个地方有一个参考副本或缓存,但我对 go 太陌生,无法找到问题。

为此,我创建了一个如下结构:

type notifier interface {
    send(message string)
}

type notificationboard struct {
    mutex    sync.mutex
    last     sync.map
    notifier notifier
}

func (n *notificationboard) init(notifier notifier) {
    n.notifier = notifier
}

// notifyless ...
func (n *notificationboard) notifyless(key string, value, threshold float64) {
    n.mutex.lock()
    defer n.mutex.unlock()

    if value >= threshold {
        fmt.printf("notificationboard.notifyless %v (value >= threshold): %v >= %v\n", key, value, threshold)
        n.last.store(key, value)
        return
    }

    // value < threshold
    if last, found := n.last.load(key); found == true {
        fmt.printf("notificationboard.notifyless %v (value < threshold): %v < %v : found %v\n", key, value, threshold, last)
        if last.(float64) >= threshold { // first trigger
            n.notifier.send(fmt.sprintf("%s < %v (%v)", key, threshold, value))
        }
    } else {
        fmt.printf("notificationboard.notifyless %v (value < threshold): %v < %v : not found\n", key, value, threshold)
        // not found, started board as less
        n.notifier.send(fmt.sprintf("%s < %v (%v)", key, threshold, value))
    }

    n.last.store(key, value)
    return
}

我知道使用sync.mutex或sync.map应该足够了,但是上面的代码两者都有,因为它是我当前的(损坏的)版本。

为了测试,我设置了以下代码:

type dummy struct{}

func (d *dummy) send(message string) {
    fmt.println("--------------> notifying", message)
}

func newboard() *notificationboard {
    notificationboard := ¬ificationboard{}
    notificationboard.init(&dummy{})
    return notificationboard
}

我还添加了一些 fmt.println 跟踪(为简洁起见,未包含在上面的代码中)并首先准备了一个单例程测试(按预期工作):

func test1(t *testing.t) {
    board := newboard()
    board.notifyless("k1", 15, 10)
    board.notifyless("k1", 10, 10)
    board.notifyless("k1", 5, 10)
    board.notifyless("k1", 4, 10)
    board.notifyless("k1", 3, 10)
    board.notifyless("k1", 10, 10)
    board.notifyless("k1", 15, 10)
    board.notifyless("k1", 20, 10)
    board.notifyless("k1", 15, 10)
    board.notifyless("k1", 10, 10)
    board.notifyless("k1", 5, 10)
    board.notifyless("k1", 1, 10)
}

输出:

> go test -run test1
notificationboard.notifyless k1 (value >= threshold): 15 >= 10
notificationboard.notifyless k1 (value >= threshold): 10 >= 10
notificationboard.notifyless k1 (value < threshold): 5 < 10 : found 10
--------------> notifying k1 < 10 (5)
notificationboard.notifyless k1 (value < threshold): 4 < 10 : found 5
notificationboard.notifyless k1 (value < threshold): 3 < 10 : found 4
notificationboard.notifyless k1 (value >= threshold): 10 >= 10
notificationboard.notifyless k1 (value >= threshold): 15 >= 10
notificationboard.notifyless k1 (value >= threshold): 20 >= 10
notificationboard.notifyless k1 (value >= threshold): 15 >= 10
notificationboard.notifyless k1 (value >= threshold): 10 >= 10
notificationboard.notifyless k1 (value < threshold): 5 < 10 : found 10
--------------> notifying k1 < 10 (5)
notificationboard.notifyless k1 (value < threshold): 1 < 10 : found 5
pass

我们可以看到输出“notifying ....”发生了两次,仅在样本低于阈值的时刻

但是后来,我创建了一个多 gooutine 测试,然后通知发生多次:

func test3(t *testing.t) {
    preparing := sync.waitgroup{}
    preparing.add(1)

    board := newboard()
    wg := sync.waitgroup{}

    for i := 0; i < 30; i++ {
        wg.add(1)
        go func(x int, not *notificationboard) {
            fmt.printf("routine %v waiting preparation... \n", x)
            preparing.wait()

            for j := 15.0; j > 5; j-- {
                fmt.printf("routine %v notifying %v\n", x, j)
                not.notifyless("keyx", j+float64(x+1)/100, 10)
            }

            wg.done()
        }(i, board)
    }

    preparing.done()
    wg.wait()

}

输出:

> go test -run test3
routine 7 waiting preparation...
routine 2 waiting preparation...
routine 2 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.03 >= 10
routine 2 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.03 >= 10
routine 2 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.03 >= 10
routine 2 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.03 >= 10
routine 2 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.03 >= 10
routine 2 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.03 >= 10
routine 2 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.03 < 10 : found 10.03
--------------> notifying keyx < 10 (9.03)
routine 2 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.03 < 10 : found 9.03
routine 2 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.03 < 10 : found 8.03
routine 2 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.03 < 10 : found 7.03
routine 14 waiting preparation...
routine 14 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.15 >= 10
routine 14 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.15 >= 10
routine 14 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.15 >= 10
routine 14 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.15 >= 10
routine 14 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.15 >= 10
routine 14 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.15 >= 10
routine 14 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.15 < 10 : found 10.15
--------------> notifying keyx < 10 (9.15)
routine 14 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.15 < 10 : found 9.15
routine 14 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.15 < 10 : found 8.15
routine 14 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.15 < 10 : found 7.15
routine 22 waiting preparation...
routine 27 waiting preparation...
routine 27 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.28 >= 10
routine 27 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.28 >= 10
routine 27 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.28 >= 10
routine 27 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.28 >= 10
routine 27 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.28 >= 10
routine 27 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.28 >= 10
routine 27 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.28 < 10 : found 10.28
--------------> notifying keyx < 10 (9.28)
routine 27 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.28 < 10 : found 9.28
routine 27 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.28 < 10 : found 8.28
routine 27 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.28 < 10 : found 7.28
routine 20 waiting preparation...
routine 20 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.21 >= 10
routine 20 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.21 >= 10
routine 20 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.21 >= 10
routine 20 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.21 >= 10
routine 20 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.21 >= 10
routine 20 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.21 >= 10
routine 20 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.21 < 10 : found 10.21
--------------> notifying keyx < 10 (9.21)
routine 20 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.21 < 10 : found 9.21
routine 20 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.21 < 10 : found 8.21
routine 20 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.21 < 10 : found 7.21
routine 19 waiting preparation...
routine 19 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.2 >= 10
routine 19 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.2 >= 10
routine 19 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.2 >= 10
routine 19 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.2 >= 10
routine 19 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.2 >= 10
routine 19 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.2 >= 10
routine 19 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.2 < 10 : found 10.2
--------------> notifying keyx < 10 (9.2)
routine 19 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.2 < 10 : found 9.2
routine 19 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.2 < 10 : found 8.2
routine 19 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.2 < 10 : found 7.2
routine 0 waiting preparation...
routine 0 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.01 >= 10
routine 0 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.01 >= 10
routine 0 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.01 >= 10
routine 0 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.01 >= 10
routine 0 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.01 >= 10
routine 0 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.01 >= 10
routine 0 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.01 < 10 : found 10.01
--------------> notifying keyx < 10 (9.01)
routine 0 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.01 < 10 : found 9.01
routine 0 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.01 < 10 : found 8.01
routine 0 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.01 < 10 : found 7.01
routine 17 waiting preparation...
routine 17 notifying 15
notificationboard.notifyless keyx (value >= threshold): 15.18 >= 10
routine 17 notifying 14
notificationboard.notifyless keyx (value >= threshold): 14.18 >= 10
routine 17 notifying 13
notificationboard.notifyless keyx (value >= threshold): 13.18 >= 10
routine 17 notifying 12
notificationboard.notifyless keyx (value >= threshold): 12.18 >= 10
routine 17 notifying 11
notificationboard.notifyless keyx (value >= threshold): 11.18 >= 10
routine 17 notifying 10
notificationboard.notifyless keyx (value >= threshold): 10.18 >= 10
routine 17 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.18 < 10 : found 10.18
--------------> notifying keyx < 10 (9.18)
routine 17 notifying 8
notificationboard.notifyless keyx (value < threshold): 8.18 < 10 : found 9.18
routine 17 notifying 7
notificationboard.notifyless keyx (value < threshold): 7.18 < 10 : found 8.18
routine 17 notifying 6
notificationboard.notifyless keyx (value < threshold): 6.18 < 10 : found 7.18
routine 15 waiting preparation...
routine 16 waiting preparation...
...... continues

我添加了一个十进制值来表示 goroutine,并查看输出,似乎每个 goroutine 都有自己的映射副本,因为它们正在查找具有相同小数位的先前值。但后来我发现:

...
notificationboard.notifyless keyx (value >= threshold): 10.22 >= 10
routine 21 notifying 9
notificationboard.notifyless keyx (value >= threshold): 10.07 >= 10
routine 6 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.08 < 10 : found 10.07
--------------> notifying keyx < 10 (9.08)
routine 7 notifying 8
notificationboard.notifyless keyx (value < threshold): 9.17 < 10 : found 9.08
routine 16 notifying 8
notificationboard.notifyless keyx (value >= threshold): 10.11 >= 10
routine 10 notifying 9
notificationboard.notifyless keyx (value < threshold): 9.3 < 10 : found 10.11
--------------> notifying keyx < 10 (9.3)
routine 29 notifying 8
notificationboard.notifyless keyx (value < threshold): 9.19 < 10 : found 9.3
routine 18 notifying 8
...

这表明他们还从其他 goroutine 中查找先前的值。

我很确定这是一个基本的并发问题,但我无法发现它。 :(

我正在使用:

> go version
go version go1.10.2 windows/amd64

有什么想法吗?


解决方案


简化逻辑的一种方法可能是运行一个修改映射的 goroutine。然后,它可以侦听通道上的新值(因为如果按顺序处理值应该没问题)。您需要小心地知道您的 goroutine 何时返回,以确保它不会泄漏。一般来说,您不应该在 goroutine 之间共享数据,您应该使用通道在 goroutine 之间进行通信https://gobyexample.com/channels 是一个很好的渠道介绍。 https://blog.golang.org/share-memory-by-communicating很好地解释了“不要通过共享内存来通信,而是通过通信来共享内存。”

以下示例说明了如何使用通道而不是共享内存来实现此类应用程序 (playground version)。

package main

import (
    "fmt"
    "sync"
)

type value struct {
    key       string
    value     float64
    threshold float64
}

func main() {
    b := board{
        last: map[string]float64{},
    }
    c := b.start()
    wg := sync.waitgroup{}
    for i := 0; i < 30; i++ {
        wg.add(1)
        go func(i int) {
            for j := 15.0; j > 5; j-- {
                c <- value{"k1", j + float64(i+1)/100, 10}
            }
            wg.done()
        }(i)
    }
    wg.wait()
    close(c)
}

type board struct {
    last map[string]float64
}

func (b *board) start() chan<- value {
    c := make(chan value)
    go func() {
        for v := range c {
            b.notify(v)
        }
    }()
    return c
}

func (b *board) notify(v value) {
    if l, ok := b.last[v.key]; !ok || l >= v.threshold {
        if v.value < v.threshold {
            fmt.printf("%s < %v (%v)\n", v.key, v.threshold, v.value)
        }
    }
    b.last[v.key] = v.value
}

我认为您在设置此类跟踪器时需要设置标志,一个用于值上升时,另一个用于值下降时。我实现了一个

package main

import (
    "fmt"
    "sync"
)

const (
    threshold      int = 10
    upperThreshold int = 20
)

var mu sync.Mutex
var downwatch bool
var upwatch bool

func main() {
    var tracker int = 10
    var temp int = 1
    var sign int = 1
    for i := 1; i < 20; i++ {
        sign = sign * -1
        temp = temp + i
        go UpdateTracker(&tracker, temp*sign)
    }

    for {
    }
    return
}
func SetDownWatch() {
    downwatch = true
}
func SetUpWatch() {
    upwatch = true
}
func UnSetDownWatch() {
    downwatch = false
}
func UnSetUpWatch() {
    upwatch = false
}

func UpdateTracker(tracker *int, val int) {
    mu.Lock()
    defer mu.Unlock()
    if !(upwatch || downwatch) {
        if (*tracker)+val < threshold {
            NotifyOnDrop()
            SetDownWatch()
        }
        if (*tracker + val) > upperThreshold {
            NotifyOnRise()
            SetUpWatch()
        }

    }
    if (*tracker)+val < threshold && upwatch {
        NotifyOnDrop()
        SetDownWatch()
        UnSetUpWatch()
    }

    if (*tracker+val) > upperThreshold && downwatch {
        NotifyOnRise()
        SetUpWatch()
        UnSetDownWatch()
    }

    *tracker = (*tracker) + val
    fmt.Println((*tracker))
    return
}

func NotifyOnDrop() {
    fmt.Println("dropped")
    return
}

func NotifyOnRise() {
    fmt.Println("rose")
    return
}

updatetracker 作为 go 例程运行,并在值超过设置阈值时打印到控制台。我认为这就是您正在寻找的功能,这里缺少的是 last.store 函数,我相信它是您的代码自定义的。我确信还有其他方法可以处理这个问题。这对我来说似乎很简单。

文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《如何跨 goroutine 共享地图》文章吧,也可关注golang学习网公众号了解相关技术文章。

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
思锐光学全新夜行者16/75mm T1.2电影镜头5月7日亮相,支持多卡口选择思锐光学全新夜行者16/75mm T1.2电影镜头5月7日亮相,支持多卡口选择
上一篇
思锐光学全新夜行者16/75mm T1.2电影镜头5月7日亮相,支持多卡口选择
Win10资源管理器无限闪退怎么办?Win10资源管理器无限闪退怎么解决
下一篇
Win10资源管理器无限闪退怎么办?Win10资源管理器无限闪退怎么解决
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    2次使用
  • Brev AI:零注册门槛的全功能免费AI音乐创作平台
    Brev AI
    探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
    2次使用
  • AI音乐实验室:一站式AI音乐创作平台,助力音乐创作
    AI音乐实验室
    AI音乐实验室(https://www.aimusiclab.cn/)是一款专注于AI音乐创作的平台,提供从作曲到分轨的全流程工具,降低音乐创作门槛。免费与付费结合,适用于音乐爱好者、独立音乐人及内容创作者,助力提升创作效率。
    2次使用
  • SEO标题PixPro:AI驱动网页端图像处理平台,提升效率的终极解决方案
    PixPro
    SEO摘要PixPro是一款专注于网页端AI图像处理的平台,提供高效、多功能的图像处理解决方案。通过AI擦除、扩图、抠图、裁切和压缩等功能,PixPro帮助开发者和企业实现“上传即处理”的智能化升级,适用于电商、社交媒体等高频图像处理场景。了解更多PixPro的核心功能和应用案例,提升您的图像处理效率。
    2次使用
  • EasyMusic.ai:零门槛AI音乐生成平台,专业级输出助力全场景创作
    EasyMusic
    EasyMusic.ai是一款面向全场景音乐创作需求的AI音乐生成平台,提供“零门槛创作 专业级输出”的服务。无论你是内容创作者、音乐人、游戏开发者还是教育工作者,都能通过EasyMusic.ai快速生成高品质音乐,满足短视频、游戏、广告、教育等多元需求。平台支持一键生成与深度定制,积累了超10万创作者,生成超100万首音乐作品,用户满意度达99%。
    3次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码