并发客户端的goroutines或锁

huangapple go评论113阅读模式
英文:

Goroutines or locks for concurrent clients

问题

通常在编写具有多个goroutine的并发程序时,这些goroutine需要访问一个客户端,例如编写一个REST API,其中每个HTTP处理程序都需要使用一个已初始化的Redis客户端来读取和写入Redis实例。

我可以使用带有互斥锁的客户端实例,以便一次只能有一个goroutine使用它,或者使用一个客户端goroutine,其他goroutine可以通过通道请求读取。这两种方式都可以工作,但我想知道哪种方式更符合惯例?谢谢你的帮助。

英文:

Often when writing concurrent programs with multiple goroutines, those goroutines require access to a client, for instance writing a REST API where each HTTP handler needs to use a single initialized Redis client to read and write to the Redis instance.

I either have a client instance with a mutex lock so only one goroutine can use it at any one time OR have a client goroutine which the other goroutines can request a read via a channel. Both ways work but I am wondering which is more idiomatic? Thanks for your help

答案1

得分: 2

如果您只有一个客户端,并且只需要对其执行简单的操作,请使用互斥锁。它通常简单易懂,不像使用一堆通道和select语句的goroutine。请确保封装线程安全性,以免给API用户带来锁的负担。

比较一下:

  1. var (
  2. mutex sync.Mutex
  3. resource = 0
  4. )
  5. func Inc() int {
  6. mutex.Lock()
  7. defer mutex.Unlock()
  8. resource++
  9. return resource
  10. }

与:

  1. var requests = make(chan chan int)
  2. func init() {
  3. go func() {
  4. resource := 0
  5. for {
  6. response := <-requests
  7. resource++
  8. response <- resource
  9. }
  10. }()
  11. }
  12. func Inc() int {
  13. response := make(chan int)
  14. requests <- response
  15. return <-response
  16. }

前者显然更简洁和可维护。特别是如果资源不是全局的,通道方法还需要手动管理goroutine,因为goroutine不会被垃圾回收(参见如何停止goroutine)。


如果您不介意有多个客户端,请使用客户端池。go-redis原生支持连接池。连接池本身是线程安全的,可以用于获取其中一个空闲连接。

英文:

If you have only one client, and only simple operations to perform on it, use a mutex. It's typically simple and easy to understand, unlike a goroutine with a bunch of channels and a select statement. Be sure to encapsulate the thread-safety in order not to burden the API user with locks.

Compare:

  1. var (
  2. mutex sync.Mutex
  3. resource = 0
  4. )
  5. func Inc() int {
  6. mutex.Lock()
  7. defer mutex.Unlock()
  8. resource++
  9. return resource
  10. }

With:

  1. var requests = make(chan chan int)
  2. func init() {
  3. go func() {
  4. resource := 0
  5. for {
  6. response := &lt;- requests
  7. resource++
  8. response &lt;- resource
  9. }
  10. }()
  11. }
  12. func Inc() int {
  13. response := make(chan int)
  14. requests &lt;- response
  15. return &lt;-response
  16. }

The former is clearly more concise and maintainable. And especially if the resource isn't global, the channel approach also requires manual management of goroutines since goroutines aren't garbage-collected (see how to stop a goroutine).


If you don't mind having more than one client, use a pool of clients. go-redis supports pooling out of the box. The pool itself is thread-safe and can be used to acquire one of the idle connections.

huangapple
  • 本文由 发表于 2015年4月22日 21:44:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/29799517.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定