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

Golang,如何共享价值——消息还是互斥?

Golang,如何共享价值——消息还是互斥?

守候你守候我 2021-08-23 17:12:26
我已经完成了简单的基准测试,在消息传递和共享值锁定中哪一个更有效。首先,请检查下面的代码。package mainimport (    "flag"    "fmt"    "math/rand"    "runtime"    "sync"    "time")type Request struct {    Id      int    ResChan chan Response}type Response struct {    Id    int    Value int}func main() {    procNum := flag.Int("proc", 1, "Number of processes to use")    clientNum := flag.Int("client", 1, "Number of clients")    mode := flag.String("mode", "message", "message or mutex")    flag.Parse()    if *procNum > runtime.NumCPU() {        *procNum = runtime.NumCPU()    }    fmt.Println("proc:", *procNum)    fmt.Println("client:", *clientNum)    fmt.Println("mode:", *mode)    runtime.GOMAXPROCS(*procNum)    rand.Seed(time.Now().UnixNano())    var wg sync.WaitGroup    sharedValue := 0    start := time.Now()    if *mode == "message" {        reqChan := make(chan Request) // increasing channel size does not change the result        go func() {            for {                req := <-reqChan                sharedValue++                req.ResChan <- Response{Id: req.Id, Value: sharedValue}            }        }()        for i := 0; i < *clientNum; i++ {            wg.Add(1)            go func(index int) {                defer wg.Done()                c := make(chan Response)                defer close(c)                id := rand.Int()                reqChan <- Request{Id: id, ResChan: c}                <-c            }(i)        }    } else if *mode == "mutex" {        mutex := &sync.Mutex{}        for i := 0; i < *clientNum; i++ {            wg.Add(1)            go func(index int) {                defer wg.Done()                mutex.Lock()                sharedValue++                mutex.Unlock()            }(i)        }    }正如您已经注意到的,程序相对简单。在消息模式下,它通过消息传递增加 sharedValue。在互斥模式下,它通过锁定来增加sharedValue。我曾尝试仅使用一个通道进行消息模式并放弃了。我想这可能是不可能的,不是吗?我的电脑有 2 个 Xeon CPU,每个 CPU 有 6 个内核。由于超线程,逻辑上有 24 个内核可用。其内存大小为 12G。如果我使用任意数量的标志运行程序,互斥模式总是至少快 2 倍(通常是 3 倍)。好的,我可以理解管理渠道需要一定的成本。那么,如果我只考虑性能,是否有任何理由使用通道而不是互斥锁?另外,消息传递的成本是否可以被巨大的消息忽略?
查看完整描述

3 回答

?
FFIVE

TA贡献1797条经验 获得超6个赞

如果我只考虑性能,是否有任何理由使用通道而不是互斥锁?

不是真的。维基页面“使用一个sync.Mutex频道还是一个频道? ”说使用最有表现力和/或最简单的那个。
有一个用于 Mutex 的通道示例,但正如评论的那样:

虽然通道为受保护的数据提供了一个很好的解决方案,但在一个作者和多个读者的情况下,它是一个效率较低的解决方案。

此线程添加:

如果您正在共享数据,并且从不阻塞锁定部分,只需使用互斥锁。
在非阻塞情况下,互斥量真的很便宜

如果您有一些共享服务执行复杂或冗长的操作,并且必须对其进行序列化,请考虑为其提供自己的 goroutine,该 goroutine 从通道接收请求并在完成后发回回复。通常你发送一个struct带有输入参数和一个用于回复的通道对象。
这很像 RPC。

通道用于通信,而不是锁定。
如果您仅出于锁定目的通过通道发送无意义的数据,那么您可能会使事情变得过于复杂。


查看完整回答
反对 回复 2021-08-23
?
慕妹3146593

TA贡献1820条经验 获得超9个赞

VonC 描述了您观察到的结果背后的具体原因。在简单的情况下,互斥体是高效的,因为它们是极简主义的,而通道较少,因为还有很多工作要做,尤其是在将数据构造为Response实例的示例代码中。

您的测试程序很容易得出一个天真的结论,即互斥锁就是您所需要的,共享内存就足够了,而通道是一个浪费且不必要的好主意。那么为什么 Go 的创始人推荐通过通信来共享内存,而不是通过共享内存来通信呢?

并发不仅仅是锁定共享数据。Communicating Sequential Processes (CSP) 背后的整个前提是,系统从根本上由进程(这里也称为 goroutines)组成,这些进程通过事件的交换与彼此以及与外部世界进行交互,这些事件可能是携带信息的消息。这个模型是递归的:进程本身可能包含更小的进程来做事,通过事件的交换相互交互。

因此,Go 作为语言的关键部分所支持的通道通信模型是可扩展的。可以在小规模上描述并发组件并使用组件来构建更大的组件,等等。您可以通过这种方式自然地描述高度并发的系统。

如果您尝试仅使用互斥锁来设计并发系统,您会感到沮丧并发现您必须编写主要是顺序的代码。在某些情况下,最终的性能可能会更好,但在系统的表达能力和并行执行范围方面可能会产生巨大的反成本。

如果您开始考虑如何保护共享数据免受竞争条件的影响,您将引导自己进入适合互斥锁的设计,因为通道效率太低,因此没有相关性。

多读取器单写入器共享数据的简单情况经常出现,值得使用互斥锁解决方案。但有时这可能意味着忽略基于具有多个客户端的服务的更通用的解决方案。

最终,所有软件设计都需要评估权衡并以一种或另一种方式做出决定。在 Go 中,您可以在适当的时候选择使用通道和进程组合(即 goroutines)。很少有其他语言提供这一点。(奥卡姆是我所知道的唯一一个至少与围棋一样好)。


查看完整回答
反对 回复 2021-08-23
  • 3 回答
  • 0 关注
  • 139 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号