通过golang中的通道进行双向通信

时间:2017-06-07 22:26:24

标签: multithreading go concurrency locking

我有几个函数,我希望它们以原子方式执行,因为它们处理敏感的数据结构。假设以下场景: 有两个函数:lock(sth)unlock(sth),可以通过goroutine随时调用以在全局数组中锁定或解锁sth。我正在考虑使用命令通道,以便goroutines将lockunlock命令发送到通道,并在通道的接收端,某种handler句柄{{1} },lock按顺序通过从通道中获取命令来请求。那很好,但是如果unlock想要将结果发送回请求者呢?是否可以使用golang频道?我知道可以使用某种锁定机制,如互斥锁,但我想知道是否有可能使用这种用例的通道?我在某处看到建议使用channel而不是goland低级锁结构。

用一句话:

在容量为1的通道中,我希望接收方能够回复发送消息的goroutine。

或等效地:

goroutine向频道发送内容;该消息由另一个goroutine接收并处理导致一些结果;发件人如何意识到结果?

3 个答案:

答案 0 :(得分:0)

sync包中包含一个互斥锁sync.Mutex,可以通过线程安全方式从任何goroutine锁定和解锁。而不是使用通道发送命令来锁定某些内容,如何仅使用发件人的互斥锁?

mutex := new(sync.Mutex)
sensitiveData := make([]string, 0)
// when someone wants to operate on a sensitiveData,
// ...
mutex.Lock()
operate(sensitiveData)
mutex.Unlock()

当你说发件人如何意识到结果时,我认为你正在谈论处理程序如何收到结果 - 那将是chan。您可以通过渠道发送数据。

或者,如果您只想感知,信号量sync.WaitGroup可能会完成这项任务。这个结构可以Add()编辑,然后发件人可以wg.Wait(),直到处理程序调用{​​{1}},这将指示发送者(正在等待)处理程序完成这样做等等。

如果您的问题是关于是使用锁定还是频道,wiki会有一个简洁的答案:

  

常见的Go新手错误是过度使用频道和goroutines只是因为它可能,和/或因为它很有趣。如果最适合您的问题,请不要害怕使用sync.Mutex。 Go是务实的,让您使用最能解决问题的工具,而不是强迫您使用一种代码。

     

作为一般指南,但是:

     

频道:传递数据所有权,分配工作单元,传达异步结果
  互斥:缓存,状态

如果你绝对想避免除wg.Done() s :)以外的任何事情,请尝试不要改变敏感数组。相反,使用通道将数据发送到不同的goroutine,在处理数据的每个步骤中,然后将处理后的数据汇集到最终类型goroutine中。也就是说,完全避免使用数组并将数据存储在chan s。

正如座右铭所说,

  

不要通过共享内存进行通信;相反,通过沟通来分享记忆。

答案 1 :(得分:0)

如果你想阻止竞争条件,那么sync原语应该可以正常工作,如@ Nevermore的回答中所述。它使代码更易读,更容易推理。

但是,如果您希望频道为您执行同步,您可以尝试以下内容:

// A global, shared channel used as a lock. Capacity of 1 allows for only
// one thread to access the protected resource at a time.
var lock = make(chan struct{}, 1)


// Operate performs the access/modification on the protected resource.
func Operate(f func() error) error {
    lock <- struct{}{}
    defer func() { <- lock }()
    return f()
}

要使用此Operate,请传入一个访问受保护资源的闭包。

// Some value that requires concurrent access.
var arr = []int{1, 2, 3, 4, 5}

// Used to sync up goroutines.
var wg sync.WaitGroup
wg.Add(len(arr))

for i := 0; i < len(arr); i++ {
    go func(j int) {
        defer wg.Done()

        // Access to arr remains protected.
        Operate(func () error {
            arr[j] *= 2
            return nil
        })

    }(i)
}
wg.Wait()

工作示例:https://play.golang.org/p/Drh-yJDVNh

或者您可以完全绕过Operate并直接使用lock以获得更多可读性:

go func(j int) {
    defer wg.Done()

    lock <- struct{}{}
    defer func() { <- lock }()

    arr[j] *= 2
}(i)

工作示例:https://play.golang.org/p/me3K6aIoR7

如您所见,此处使用频道保护arr访问权限。

答案 2 :(得分:-1)

其他问题已经很好地解决了锁定问题,但我想解决有关使用频道将响应发送回呼叫者的问题的其他部分。在发送带有请求的响应通道的Go中有一种并不罕见的模式。例如,您可以通过通道向处理程序发送命令;这些命令是struct,具有特定于实现的细节,结构将包括一个用于发送结果的通道,键入结果类型。发送的每个命令都包含一个新通道,处理程序将使用该通道发回响应,然后关闭。举例说明:

type Command struct {
    // command parameters etc
    Results chan Result
}

type Result struct {
    // Whatever a result is in this case
}

var workQueue = make(chan Command)

// Example for executing synchronously
func Example(param1 string, param2 int) Result {
    workQueue <- Command{
        Param1: param1,
        Param2: param2,
        Results: make(chan Result),
    }
    return <- Results