-1

I'm using gin gonic as an HTTP framework and I need to group some paths with shared variable by like this:

ur := r.Group("/")
ur.Use(package.Prepare)
{
    ur.GET("/", package.Home)
}

Inside the Prepare handler, I declare package variable like package.tplPath because I want all sub routes can access to this variable instead of rewrite the code in each http handler.

var tplPath = ""

func Prepare(c *gin.Context) {
    _, filename, _, _ := runtime.Caller(0)
    s := helper.RelativeFilePath(filename)
    tplPath = s[1:len(s)] + "template/"
}

I don't know how Go works with each process, and variables for each http request. If a variable was declared in package level, will it be set after each http request?

Is this considered good practice? If not, why not?

David Buck
  • 3,752
  • 35
  • 31
  • 35
TomSawyer
  • 3,711
  • 6
  • 44
  • 79

2 Answers2

4

This is not threadsafe, package variables are shared across all goroutines, and modification in one routine will change the value in all the others, causing a data race.

In general; try and avoid using package level variables where possible.

Edit:

In go, packages are a kind of module. There exists only one instance of a package for any given import path (basically package name). This means that there is only 1 instance of any variable at package level.

Package variables are shared, global state. All accessors of that variable will be accessing the exact same memory.

It does not matter what type the package variable is, struct / string / int etc. If it is defined at package level, all accessors of that variable will share the same instance of it.

If (as with http servers) you have concurrent code, there will be multiple accessors of that variable at the same time. In some situations this is fine, like when that variable is only ever read, but it appears that in your case it will be modified. Making this code racy!

Zak
  • 5,515
  • 21
  • 33
  • please explain more detail about threadsafe, avoid using package level variables, so if i declare a `struct` instead of `tplPath`, will it work, like `type pack struct { TplPath string }` – TomSawyer Jun 14 '18 at 04:16
  • Just tested then like you are correct, declared variables can be access from other request. What if using `init()` to declare the package level variables once , then i don't modife this variable during the run time, in this case `tplPath` is a private variable which means it can't be change from outsite of the package. Or what's the correct way to share variables accross the requests? – TomSawyer Jun 14 '18 at 09:18
  • Init is more of a "setup" feature; for instantiating the variable in the first place, it won't solve the fact that you are reading from and writing to the same memory at the same time – Zak Jun 14 '18 at 10:14
  • Like using mutex to lock and unlock while get/set variables? – TomSawyer Jun 15 '18 at 03:48
  • Yes, using a mutex would prevent against a data race; but often you'd want to try and re-architecht the problem or code so that you don't need to share the memory in the first place. Avoiding the need for many things to be reading and writing the same value – Zak Jun 15 '18 at 06:59
  • Thank you, i just wanna deelpy understand about how go works. My concern now is where to declare mutex variable? Declare it in package level is good practice or not? – TomSawyer Jun 15 '18 at 09:20
  • It's ok; i'd try and avoid package variables in general. but you could store one at package level and use `init()` to create the struct / mutex required – Zak Jun 15 '18 at 09:45
  • And i realize that i can get used to it, like storing variables in package level like category list, product list ... things don't need to change oftenly instead of query them from database in each request, i store it as package variables, only need to query once then i will change it when needed, sounds good? – TomSawyer Jun 15 '18 at 14:29
  • sounds like a premature optimisation. There's nothing wrong with fetching things from the database. You should consider the service object pattern instead of package variables for shared state: https://www.calhoun.io/using-the-service-object-pattern-in-go/ – Zak Jun 15 '18 at 14:32
  • serving many sql queries cause the performance. my services get overload oftenly cause by sql queries. caching is an important part of optimization, right? – TomSawyer Jun 15 '18 at 14:35
-1

The tplPath is a global variable, all routine will access the same memory address, and set after each http request. If you want set just once, and the tplPath not depend on the http request. You can set it in the init function.

func init(){
    _, filename, _, _ := runtime.Caller(0)
    s := helper.RelativeFilePath(filename)
    tplPath = s[1:len(s)] + "template/"
}

The init function will run before main, and just do once.

sakychen
  • 21
  • 1