关于GoLang的concurrent map writes错误

本文介绍了Go语言中并发编程时遇到的并发写Map问题,导致`concurrent map writes`错误。作者通过一个实例展示了如何从并发写Map引发的竞态条件,到使用WaitGroup实现并行请求但最终引发错误。解决问题的方法是为每个goroutine创建局部变量,避免直接在公共Map上进行并发写操作。修改后的代码成功避免了并发写冲突,确保了数据安全。
摘要由CSDN通过智能技术生成

作为一个GoLang萌新(其实就是并发编程萌新,之前一直在做PHP),对并发下共享资源的竞争了解不多。所以一开始写出了如下代码:

type OriginPriceController struct {
	OriginPriceService *origin_price_service.OriginPriceService
}
 
 
func (self *OriginPriceController) action() {
    var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		self.Data["product"] = self.OriginPriceService.GetOriginPriceProduct(self.C, productId)
	}()
 
	go func() {
		defer wg.Done()
		self.Data["location"] = self.OriginPriceService.GetOriginPriceLocation(self.C, locationId)
	}()
	wg.Wait()
}

轻松搞定,并行请求数据,相比之前的串行,性能基本上提升一倍。本以为并发编程也不过如此,就起几个goroutine,用WaitGroup同步多个协程间的状态,等待所有协程都执行完即可。

原本以为这样就搞定了,其实代码中隐含着一个致命问题,当多次请求测试时,很快问题就暴露出来了:

fatal error: concurrent map writes

goroutine 164 [running]:
runtime.throw(0x17e99d3, 0x15)
/usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc000069ee8 sp=0xc000069eb8 pc=0x102f6c2
runtime.mapassign_faststr(0x16fe080, 0xc000574150, 0x17e0ed5, 0x8, 0x0)
/usr/local/go/src/runtime/map_faststr.go:291 +0x3fe fp=0xc000069f50 sp=0xc000069ee8 pc=0x1014d8e
gin-frame/controllers/first_origin_price.(*FirstOriginPriceController).action.func2(0xc00029e550, 0xc00014e180, 0x571)
/Users/why/Desktop/go/gin-frame/controllers/first_origin_price/first_origin_price_controller.go:62 +0xc4 fp=0xc000069fc8 sp=0xc000069f50 pc=0x164e0d4
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1357 +0x1 fp=0xc000069fd0 sp=0xc000069fc8 pc=0x105cd81
created by gin-frame/controllers/first_origin_price.(*FirstOriginPriceController).action
/Users/why/Desktop/go/gin-frame/controllers/first_origin_price/first_origin_price_controller.go:60 +0x156
没错,就是文章题目里提到的:concurrent map writes

原来Map,对于共享变量,资源,并发写会产生竞争, 共享资源遭到破坏,解决办法是拆分成两个局部变量用于接收goroutine,在goroutine执行完之后再将结果存入公共Map,相应代码如下:

var wg sync.WaitGroup
product := make(map[string]interface{})
location := make(map[string]interface{})
wg.Add(2)
go func() {
	defer wg.Done()
	product = self.OriginPriceService.GetOriginPriceProduct(self.C, productId)
}()
 
go func() {
	defer wg.Done()
	location = self.OriginPriceService.GetOriginPriceLocation(self.C, locationId)
}()
wg.Wait()
 
self.Data["product"] = product
self.Data["location"] = location
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Go 语言的标准库中提供了 `net/http` 包,支持 HTTP 协议的客户端和服务端编程。Go 语言的并发模型让它特别适合用来编写高效的 HTTP 服务器。 使用 Go 语言编写 HTTP 服务器通常可以通过以下步骤来实现: 1. 实现一个处理器函数,该函数接受一个 `http.ResponseWriter` 和一个 `*http.Request` 参数,并使用它们来响应请求。 2. 使用 `http.HandleFunc` 函数将该处理器函数注册到一个路由上。 3. 使用 `http.ListenAndServe` 函数启动服务器。 例如,以下代码演示了如何在 8080 端口上启动一个服务器,并在根路径("/")上注册一个处理器函数,用于响应 HTTP GET 请求: ```go package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } ``` 在这个例子中,当客户端发起一个 HTTP GET 请求到根路径("/")时,服务器会调用处理器函数 `handler` 并将其写入响应。 希望这个回答对你有帮助。 ### 回答2: Go语言是一种开发高并发应用程序的优秀选择,它内置了强大的并发支持。在Go语言中,可以使用Goroutine和Channel来实现并发处理HTTP请求。 Goroutine是轻量级线程,可以在一个Go程序中同时运行多个Goroutine。可以将HTTP请求处理逻辑封装在一个Goroutine中,每个请求都会启动一个新的Goroutine来处理,从而实现并发处理。 Channel是Goroutine之间的通信机制,可以用于在Goroutine之间传递数据。在HTTP请求处理逻辑中,可以使用Channel来传递处理结果,例如将响应数据发送到一个Channel中,然后通过另一个Goroutine将数据写回响应。 使用Golang的net/http包可以方便地创建HTTP服务器。可以通过调用http.ListenAndServe函数来启动一个HTTP服务器,然后使用http.HandleFunc函数来注册处理HTTP请求的函数。在这个处理函数中,可以启动Goroutine来处理每个请求,再通过Channel将处理结果返回给主Goroutine。 在多个Goroutine之间共享数据时,需要注意并发访问的安全性。可以使用Golang提供的sync包中的互斥锁来保证并发访问的安全性。通过在访问共享数据的代码块中使用互斥锁进行锁定操作,可以防止多个Goroutine同时访问修改共享数据。 总的来说,使用Goroutine和Channel可以方便地实现Golang并发处理HTTP请求。通过合理地使用这些特性,可以提高系统的并发性能和稳定性。 ### 回答3: Golang是一种支持并发编程的编程语言,因此在Golang中实现并发的HTTP请求是非常简单的。Golang的标准库中提供了一个名为"net/http"的包,该包包含了处理HTTP请求和响应的相关函数和结构体。 通过使用Golang的HTTP库,我们可以轻松地编写并发的HTTP请求代码。首先,我们可以使用"goroutine"来创建并发的执行环境,每个goroutine都可以处理一个HTTP请求。我们可以使用"net/http"包的"Get"函数创建一个HTTP请求,并将其放入goroutine中处理。例如,下面的代码展示了如何并发地向多个URL发送HTTP请求: ```go package main import ( "fmt" "net/http" ) func main() { urls := []string{"https://www.example.com", "https://www.google.com", "https://www.github.com"} for _, url := range urls { go func(u string) { resp, err := http.Get(u) if err != nil { fmt.Printf("Error fetching %s: %s\n", u, err.Error()) return } defer resp.Body.Close() fmt.Printf("Successfully fetched %s\n", u) }(url) } // 等待所有goroutine完成 for i := 0; i < len(urls); i++ { <-done } fmt.Println("All requests finished.") } ``` 在上面的例子中,我们使用一个包含三个URL的字符串切片作为输入,然后通过循环创建了三个goroutine,并发地向这些URL发送HTTP请求。每个goroutine都会调用"Get"方法发送请求,并在请求完成后打印相应的结果。 需要注意的是,我们使用了一个无缓冲的通道"done"来等待所有的goroutine结束。每个goroutine在完成处理请求后,都会通过"done"通道发送一个信号。通过在主函数中使用"<-"操作符从通道中接收信号,我们可以保证在所有的HTTP请求完成后再继续执行下面的操作。 通过上面的示例,我们可以看到,在Golang中实现并发的HTTP请求非常简单。通过使用goroutine和"net/http"包,我们可以轻松地编写高效且可扩展的并发代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值