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

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

How to create elements of a struct with a Mutex field

问题

我有一个Get()函数:

func Get(url string) *Response {
    res, err := http.Get(url)
    if err != nil {
        return &Response{}
    }
    // 当err == nil时,res.Body != nil
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatalf("ReadAll: %v", err)
    }
    reflect.TypeOf(body)
    return &Response{sync.Mutex{}, string(body), res.StatusCode}
}

还有一个Read()函数:

func Read(url string, timeout time.Duration) (res *Response) {
    done := make(chan bool)

    go func() {
        res = Get(url)
        done <- true
    }()

    select {
    case <-done:
    case <-time.After(timeout):
        res = &Response{"Gateway timeout\n", 504}
    }

    return
}

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

type Response struct {
    Body       string
    StatusCode int
}

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

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

type Response struct {
    mu         sync.Mutex
    Body       string
    StatusCode int
}

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

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

func Get(url string) *Response {
    res, err := http.Get(url)
    if err != nil {
        return &Response{}
    }
    // 当err == nil时,res.Body != nil
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatalf("ReadAll: %v", err)
    }
    reflect.TypeOf(body)
    return &Response{sync.Mutex{}, string(body), res.StatusCode} // 这一行已更改
}

对于Read()函数:

func Read(url string, timeout time.Duration) (res *Response) {
    done := make(chan bool)
    res = &Response{sync.Mutex{}} // 添加了这一行

    go func() {
        res = Get(url)
        done <- true
    }()

    select {
    case <-done:
    case <-time.After(timeout):
        res.mu.Lock()
        res = &Response{sync.Mutex{}, "Gateway timeout\n", 504} // 在这里添加了互斥锁
    }

    defer res.mu.Unlock()
    return
}

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

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

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

英文:

I have a Get() function:

func Get(url string) *Response {
	res, err := http.Get(url)
	if err != nil {
		return &amp;Response{}
	}
	// res.Body != nil when err == nil
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalf(&quot;ReadAll: %v&quot;, err)
	}
	reflect.TypeOf(body)
	return &amp;Response{sync.Mutex(),string(body), res.StatusCode}
}

as well as a Read() function:

func Read(url string, timeout time.Duration) (res *Response) { 
	done := make(chan bool)	

	go func() {
		res = Get(url)		
		done &lt;- true		
	}()		
		select {	// As soon as either
	case &lt;-done:	// done is sent on the channel or 
	case &lt;-time.After(timeout):	// timeout
		res = &amp;Response{&quot;Gateway timeout\n&quot;, 504}

	}
	return
}

the Response type returned by the functions is defined as:

type Response struct {
	Body       string
	StatusCode int
}

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:

type Response struct {
	mu		   sync.Mutex
	Body       string
	StatusCode int
}

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():

func Get(url string) *Response {
	res, err := http.Get(url)
	if err != nil {
		return &amp;Response{}
	}
	// res.Body != nil when err == nil
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalf(&quot;ReadAll: %v&quot;, err)
	}
	reflect.TypeOf(body)
	return &amp;Response{sync.Mutex(),string(body), res.StatusCode} // This line is changed.
}

and for the Read():

func Read(url string, timeout time.Duration) (res *Response) { 
	done := make(chan bool)	
	res = &amp;Response{sync.Mutex()} // this line has been added

	go func() {	
		res = Get(url)		
		done &lt;- true		
	}()		
		select {	
	case &lt;-done:	
	case &lt;-time.After(timeout):
		res.mu.Lock()
		res = &amp;Response{sync.Mutex(), &quot;Gateway timeout\n&quot;, 504} // And mutex was added here.

	}
	defer res.mu.Unlock()
	return
}

This "solution" generates these errors:

./client.go:54: missing argument to conversion to sync.Mutex: sync.Mutex()
./client.go:63: missing argument to conversion to sync.Mutex: sync.Mutex()
./client.go:63: too few values in struct initializer
./client.go:73: missing argument to conversion to sync.Mutex: sync.Mutex()
./client.go:95: cannot use &quot;Service unavailable\n&quot; (type string) as type sync.Mutex in field value
./client.go:95: cannot use 503 (type int) as type string in field value
./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的建议使用了一个通道来解决这个问题。

func Read(url string, timeout time.Duration) (res *Response) { 
	done := make(chan bool)	// 一个通道
	resChan := make(chan *Response)

	go func() {	
		resChan <- Get(url)	
		done <- true	
	}()		
	select {	
		case <-done:
			res = &Response{}	 
		case <-time.After(timeout):
			res = &Response{"网关超时\n", 504} 
	}
	return
}

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

英文:

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

func Read(url string, timeout time.Duration) (res *Response) { 
	done := make(chan bool)	// A channel
	resChan := make(chan *Response)

	go func() {	
		resChan &lt;- Get(url)	
		done &lt;- true	
	}()		
	select {	
		case &lt;-done:
			res = &amp;Response{}	 
		case &lt;-time.After(timeout):
			res = &amp;Response{&quot;Gateway timeout\n&quot;, 504} 
	}
	return
}

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:

确定