慎写指针类型的全局变量

简介: 慎写指针类型的全局变量

简述:


关于range二三事 第二个case中,介绍了对于指针类型的 切片/map变量A 的循环,要格外注意, 迭代出的value作用域是整个方法而非循环体内.

改进办法:在循环体中引入中间变量,"暂存"下每次迭代的value的值


但对于这个A,如果是全局变量,则又极有可能出现问题:

package main
import (
  "fmt"
)
type UserInfo struct {
  Name string
  Age  int
}
var (
  defaultInfo    = UserInfo{Name: "fliter", Age: 26}
  defaultInfoSli = []*UserInfo{&defaultInfo}
)
func main() {
  for _, v := range defaultInfoSli {
    tmp := v
    //go func() {
    tmp.Age = 100
    //}()
  }
  //time.Sleep(1e9)
  fmt.Println(defaultInfoSli[0].Age)
}

defaultInfoSli迭代出的v为指针类型,tmp仍为指针类型,对其赋值,会改变全局变量defaultInfoSli的值




复现:


在具体业务场景中,服务启动时初始化(取数据库或redis,或读取配置文件,加载到内存中)了一个全局变量.每个http请求过来,golang都会有一个新的协程去处理相关逻辑. 对于某个具体方法内的变量,对每次请求都是独立和隔离(每次请求都相当于一个个cellar,彼此之间不会有干涉和影响), 但对于永久存在内存中的全局变量,如果有对其写操作,每次请求都会影响该全局变量. 当出现并发请求如用户x和y同时请求接口, 两次请求都会改写全局变量, 这时就很可能出现返回的x和y的数据错乱


Demo如下:

package main
import (
  "encoding/json"
  "fmt"
  "github.com/davecgh/go-spew/spew"
  "log"
  "net/http"
)
type BookInfo struct {
  Title string
  Rank  int
  Data  interface{}
}
var (
  defaultBook1 = BookInfo{Title: "水浒传", Rank: 1}
  defaultBook2 = BookInfo{Title: "三国演义", Rank: 2}
  defaultBook3 = BookInfo{Title: "西游记", Rank: 3}
  defaultBook4 = BookInfo{Title: "红楼梦", Rank: 4}
  DefaultBookSli = []*BookInfo{&defaultBook1, &defaultBook2, &defaultBook3, &defaultBook4}
)
type CommonParams struct {
  ID   int64
  Name string
}
var (
  ModuleHandlers = map[int]func(params *CommonParams) *BookInfo{
    1: HandleTypeOne,
    2: HandleTypeTwo,
    3: HandleTypeThree,
    4: HandleTypeFour,
  }
)
func main() {
  fmt.Println(DefaultBookSli)
  http.HandleFunc("/index", deal) //设置访问的路由
  err := http.ListenAndServe(":80", nil) //设置监听的端口
  if err != nil {
    log.Fatal("ListenAndServe: ", err)
  }
}
func deal(w http.ResponseWriter, r *http.Request) {
  name := r.URL.Query().Get("name")
  //fmt.Println("name值为:", name)
  par := &CommonParams{
    ID:   0,
    Name: name,
  }
  // 获取相关数据
  for _, v := range DefaultBookSli {
    module := v
    //fmt.Println("module is:", module)
    //fmt.Println("排序为:", module.Rank)
    m := ModuleHandlers[module.Rank](par "module.Rank")
    // 填充模块数据
    if m.Data != nil {
      module.Data = m.Data
    }
    // (如果需要),重写模块标题(在此不需要)
    //if m.Title != "" {
    //  module.Title = m.Title
    //}
  }
  //time.Sleep(1e9) //此处等待并不是因为协程,而是方便测试,不加这个等待,执行100次秒速就完成. 加这个等待是为了方便模拟"几个用户同时请求"
  //fmt.Println(DefaultBookSli[0].Rank)
  spew.Dump(DefaultBookSli)
  rsJson, _ := json.Marshal(DefaultBookSli)
  //fmt.Println(string(rsJson))    //这个写入到w的是输出到客户端的
  fmt.Fprintf(w, string(rsJson)) //这个写入到w的是输出到客户端的
}
func HandleTypeOne(p *CommonParams) *BookInfo {
  res := ""
  if p.Name == "施耐庵" {
    res = "我叫施耐庵,我是作者!"
  }
  return &BookInfo{Data: res}
}
func HandleTypeTwo(p *CommonParams) *BookInfo {
  res := ""
  if p.Name == "罗贯中" {
    res = "我叫罗贯中,我是作者!"
  }
  return &BookInfo{Data: res}
}
func HandleTypeThree(p *CommonParams) *BookInfo {
  res := ""
  if p.Name == "吴承恩" {
    res = "我叫吴承恩,我是作者!"
  }
  return &BookInfo{Data: res}
}
func HandleTypeFour(p *CommonParams) *BookInfo {
  res := ""
  if p.Name == "曹雪芹" {
    res = "我叫曹雪芹,我是作者!"
  }
  return &BookInfo{Data: res}
}

带着参数x, 使用Postman进行串行调用100次,

微信截图_20230814204041.png

同时再访问这个接口,带参数y,此时可以发现,出现了数据错乱:

微信截图_20230814204058.png

修改方案:


module := v这一步,实际上module依然是指针类型.

可以module := *v,这样module就不是指针类型,也就不会出现如上问题.


当时问题紧急,直接在里面新加了一个临时变量,即:

  // 获取相关数据
  for _, v := range DefaultBookSli {
    module := v
    var temModule = &BookInfo{
      Title: module.Title,
      Rank:  module.Rank,
    }
    m := ModuleHandlers[temModule.Rank](par "temModule.Rank")
    // 填充模块数据
    if m.Data != nil {
      module.Data = m.Data
    }
  }

详细过程参见 私有笔记 并发写全局变量导致的数据错乱问题,印象深刻的一次体验

目录
相关文章
|
6月前
|
存储 C语言
文件的类型指针
文件的类型指针
67 0
|
6月前
|
编译器 C语言
void的指针类型
void的指针类型
44 0
|
6月前
|
存储 程序员 C++
在C++编程语言中指针的作用类型
在C++编程语言中指针的作用类型
71 0
|
1月前
|
C语言 C++
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
|
2月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
3月前
|
存储 安全 Go
深入理解 Go 语言中的指针类型
【8月更文挑战第31天】
44 0
|
5月前
|
编译器 C++
函数指针和函数对象不是同一类型怎么替换
函数指针和函数对象不是同一类型,为何可替换用作同一函数的参数
|
6月前
|
存储 安全 C语言
void指针类型详解
void指针类型详解
75 2
|
5月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
49 0
|
5月前
|
图形学 Windows
程序技术好文:记录类型指针
程序技术好文:记录类型指针
26 0