如何创建一个包含 Mutex 字段的结构体的元素

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

How to create elements of a struct with a Mutex field

问题

我有一个Get()函数:

  1. func Get(url string) *Response {
  2. res, err := http.Get(url)
  3. if err != nil {
  4. return &Response{}
  5. }
  6. // 当err == nil时,res.Body != nil
  7. defer res.Body.Close()
  8. body, err := ioutil.ReadAll(res.Body)
  9. if err != nil {
  10. log.Fatalf("ReadAll: %v", err)
  11. }
  12. reflect.TypeOf(body)
  13. return &Response{sync.Mutex{}, string(body), res.StatusCode}
  14. }

还有一个Read()函数:

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool)
  3. go func() {
  4. res = Get(url)
  5. done <- true
  6. }()
  7. select {
  8. case <-done:
  9. case <-time.After(timeout):
  10. res = &Response{"Gateway timeout\n", 504}
  11. }
  12. return
  13. }

函数返回的Response类型定义如下:

  1. type Response struct {
  2. Body string
  3. StatusCode int
  4. }

这个Read()函数使用了Get()函数,并实现了超时功能。问题在于,如果超时发生并且在Read()中同时将Get()的响应写入res,就会发生数据竞争。

我有一个解决方案,就是使用互斥锁(Mutex)。为此,我会在Response结构体中添加一个字段:

  1. type Response struct {
  2. mu sync.Mutex
  3. Body string
  4. StatusCode int
  5. }

这样,就可以对Response进行加锁。然而,我不确定如何在代码的其他部分进行修复。

我的尝试如下,对于Get()函数:

  1. func Get(url string) *Response {
  2. res, err := http.Get(url)
  3. if err != nil {
  4. return &Response{}
  5. }
  6. // 当err == nil时,res.Body != nil
  7. defer res.Body.Close()
  8. body, err := ioutil.ReadAll(res.Body)
  9. if err != nil {
  10. log.Fatalf("ReadAll: %v", err)
  11. }
  12. reflect.TypeOf(body)
  13. return &Response{sync.Mutex{}, string(body), res.StatusCode} // 这一行已更改
  14. }

对于Read()函数:

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool)
  3. res = &Response{sync.Mutex{}} // 添加了这一行
  4. go func() {
  5. res = Get(url)
  6. done <- true
  7. }()
  8. select {
  9. case <-done:
  10. case <-time.After(timeout):
  11. res.mu.Lock()
  12. res = &Response{sync.Mutex{}, "Gateway timeout\n", 504} // 在这里添加了互斥锁
  13. }
  14. defer res.mu.Unlock()
  15. return
  16. }

这个“解决方案”会生成以下错误:

  1. ./client.go:54: 缺少sync.Mutex的转换参数: sync.Mutex()
  2. ./client.go:63: 缺少sync.Mutex的转换参数: sync.Mutex()
  3. ./client.go:63: 结构体初始化器中的值过少
  4. ./client.go:73: 缺少sync.Mutex的转换参数: sync.Mutex()
  5. ./client.go:95: 无法将“Service unavailable\n”(类型为string)用作sync.Mutex类型的字段值
  6. ./client.go:95: 无法将503(类型为int)用作string类型的字段值
  7. ./client.go:95: 结构体初始化器中的值过少

在这种情况下,使用互斥锁的正确方法是什么?

英文:

I have a Get() function:

  1. func Get(url string) *Response {
  2. res, err := http.Get(url)
  3. if err != nil {
  4. return &amp;Response{}
  5. }
  6. // res.Body != nil when err == nil
  7. defer res.Body.Close()
  8. body, err := ioutil.ReadAll(res.Body)
  9. if err != nil {
  10. log.Fatalf(&quot;ReadAll: %v&quot;, err)
  11. }
  12. reflect.TypeOf(body)
  13. return &amp;Response{sync.Mutex(),string(body), res.StatusCode}
  14. }

as well as a Read() function:

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool)
  3. go func() {
  4. res = Get(url)
  5. done &lt;- true
  6. }()
  7. select { // As soon as either
  8. case &lt;-done: // done is sent on the channel or
  9. case &lt;-time.After(timeout): // timeout
  10. res = &amp;Response{&quot;Gateway timeout\n&quot;, 504}
  11. }
  12. return
  13. }

the Response type returned by the functions is defined as:

  1. type Response struct {
  2. Body string
  3. StatusCode int
  4. }

This read function makes use of the Get() function and also implements a timeout. The problem is that a data race can occur if the timeout occurs and the Get() response is written to res at the same time in Read().

I have a plan for how to solve this. It is to use Mutex. To do this, I would add a field to the Response struct:

  1. type Response struct {
  2. mu sync.Mutex
  3. Body string
  4. StatusCode int
  5. }

so that the Response can be locked. However, I'm not sure how to fix this in the other parts of the code.

My attempt looks like this, for the Get():

  1. func Get(url string) *Response {
  2. res, err := http.Get(url)
  3. if err != nil {
  4. return &amp;Response{}
  5. }
  6. // res.Body != nil when err == nil
  7. defer res.Body.Close()
  8. body, err := ioutil.ReadAll(res.Body)
  9. if err != nil {
  10. log.Fatalf(&quot;ReadAll: %v&quot;, err)
  11. }
  12. reflect.TypeOf(body)
  13. return &amp;Response{sync.Mutex(),string(body), res.StatusCode} // This line is changed.
  14. }

and for the Read():

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool)
  3. res = &amp;Response{sync.Mutex()} // this line has been added
  4. go func() {
  5. res = Get(url)
  6. done &lt;- true
  7. }()
  8. select {
  9. case &lt;-done:
  10. case &lt;-time.After(timeout):
  11. res.mu.Lock()
  12. res = &amp;Response{sync.Mutex(), &quot;Gateway timeout\n&quot;, 504} // And mutex was added here.
  13. }
  14. defer res.mu.Unlock()
  15. return
  16. }

This "solution" generates these errors:

  1. ./client.go:54: missing argument to conversion to sync.Mutex: sync.Mutex()
  2. ./client.go:63: missing argument to conversion to sync.Mutex: sync.Mutex()
  3. ./client.go:63: too few values in struct initializer
  4. ./client.go:73: missing argument to conversion to sync.Mutex: sync.Mutex()
  5. ./client.go:95: cannot use &quot;Service unavailable\n&quot; (type string) as type sync.Mutex in field value
  6. ./client.go:95: cannot use 503 (type int) as type string in field value
  7. ./client.go:95: too few values in struct initializer

What is the correct way of using Mutex in this case?

答案1

得分: 2

虽然你在 Volker 的指导下给出的答案很好,但你可能想考虑使用一个非默认的 http.Client,这样你就可以在发出请求的客户端上设置一个 Timeout(这样你就不必担心自己处理超时问题了)。

英文:

While your answer with Volker's guidance is good, you might want to consider using a non default http.Client so that you can set a Timeout on the client making the request (then you don't have to worry about handling the timeouts yourself).

答案2

得分: 1

我按照Volker的建议使用了一个通道来解决这个问题。

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool) // 一个通道
  3. resChan := make(chan *Response)
  4. go func() {
  5. resChan <- Get(url)
  6. done <- true
  7. }()
  8. select {
  9. case <-done:
  10. res = &Response{}
  11. case <-time.After(timeout):
  12. res = &Response{"网关超时\n", 504}
  13. }
  14. return
  15. }

现在,对res的写入不会同时发生。它要么是超时,要么是Get(url)的返回值。

英文:

I followed Volker's suggestion and used a channel to solve the problem.

  1. func Read(url string, timeout time.Duration) (res *Response) {
  2. done := make(chan bool) // A channel
  3. resChan := make(chan *Response)
  4. go func() {
  5. resChan &lt;- Get(url)
  6. done &lt;- true
  7. }()
  8. select {
  9. case &lt;-done:
  10. res = &amp;Response{}
  11. case &lt;-time.After(timeout):
  12. res = &amp;Response{&quot;Gateway timeout\n&quot;, 504}
  13. }
  14. return
  15. }

Now, there can be no simultaneous writes to res. It's going to be either the timeout or the returned value of Get(url).

huangapple
  • 本文由 发表于 2016年4月20日 16:58:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/36738688.html
匿名

发表评论

匿名网友

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

确定