【问题标题】:Golang, how to share value - message or mutex?Golang,如何共享价值 - 消息或互斥锁?
【发布时间】:2014-12-18 17:41:42
【问题描述】:

我已经完成了简单的基准测试,哪一个在消息传递和共享值锁定方面效率更高。

首先,请检查下面的代码。

package main

import (
    "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)
        }
    }

    wg.Wait()
    elapsed := time.Since(start)

    fmt.Println("Elapsed:", elapsed, "value:", sharedValue)
}

正如您已经注意到的,程序相对简单。 消息模式,通过消息传递增加sharedValue。在互斥模式下,它通过锁定增加sharedValue

我曾尝试仅将一个频道用于消息模式并放弃了。我想这可能是不可能的,不是吗?

我的计算机有 2 个 Xeon CPU,每个 CPU 有 6 个内核。由于超线程,逻辑上可以使用 24 个内核。它的内存大小为 12G。

如果我使用任意数量的标志运行程序,互斥模式总是更快至少 2 倍(通常是 3 倍)。

好的,我可以理解管理渠道需要一定的成本。那么,如果我只考虑性能,是否有任何理由使用通道而不是互斥锁?另外,如果消息量很大,消息传递成本可以忽略吗?

【问题讨论】:

    标签: go mutex channel


    【解决方案1】:

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

    不是真的。维基页面“Use a sync.Mutex or a channel?”说要使用最有表现力和/或最简单的那个。
    有一个example of channel used for Mutex,但是as commented

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

    This threads 补充:

    如果您要共享数据,并且从不在锁定部分阻塞,只需使用互斥锁即可。
    互斥锁在非阻塞情况下非常便宜

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

    频道用于交流,而不是锁定。
    如果您通过通道发送无意义的数据仅用于锁定目的,那么您就是 可能过于复杂了。

    【讨论】:

      【解决方案2】:

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

      您的测试程序很容易得出这样一个幼稚的结论:互斥体就是您所需要的,共享内存就足够了,而通道是一种浪费且不必要的好主意。那么为什么 Go 的创始人建议通过通信共享内存,而不是通过共享内存进行通信

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

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

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

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

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

      归根结底,所有软件设计都需要评估权衡并以一种或另一种方式做出决策。在 Go 中,您可以在适当的时候选择使用通道和进程组合(即 goroutines)。很少有其他语言提供此功能。 (据我所知,Occam 是唯一一个性能至少和围棋一样好的人)。

      【讨论】:

        【解决方案3】:

        使用哪一个很重要。通道是一次性的,对共享数据进行读/写。如果您需要在初始化一次后保留一些共享数据,那么您必须设置某种变量并用锁来获取它。一个示例可能是停止通道。

        // thread two spawned right before thread one reached the stop case.
        select {
        case <-stop // thread one reaches stop case and does stuff.
        // do stuff
        default: // a few nanoseconds later, thread two breaches the stop case.
        // do other stuff
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-01-04
          • 2021-10-10
          • 2012-06-29
          • 2021-08-19
          • 2011-12-28
          • 1970-01-01
          • 1970-01-01
          • 2011-07-22
          相关资源
          最近更新 更多