英文:
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 &Response{}
}
// res.Body != nil when err == 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}
}
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 <- true
}()
select { // As soon as either
case <-done: // done is sent on the channel or
case <-time.After(timeout): // timeout
res = &Response{"Gateway timeout\n", 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 &Response{}
}
// res.Body != nil when err == 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} // This line is changed.
}
and for the Read()
:
func Read(url string, timeout time.Duration) (res *Response) {
done := make(chan bool)
res = &Response{sync.Mutex()} // this line has been added
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} // 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 "Service unavailable\n" (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 <- Get(url)
done <- true
}()
select {
case <-done:
res = &Response{}
case <-time.After(timeout):
res = &Response{"Gateway timeout\n", 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)
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论