golang 资源所有权模式(文件、连接、可关闭对象)
golang resource ownership pattern (files, connections, close-ables)
在 golang 中管理资源所有权的正确方法是什么?假设我有以下内容:
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
db.Close()
总是让调用函数保持所有权和关闭资源的责任是典型的吗?这对我来说感觉有点奇怪,因为关闭后,am
仍然保留一个引用,如果我或其他人以后不小心,可以尝试使用 db
(我想这是延迟的情况;但是,如果我想从这个块返回 ResourceManager am
,我怎么才能正确地推迟文件的关闭呢?我实际上希望它在这个块完成执行时保持打开状态)。我发现在其他语言中,我经常希望允许实例管理资源,然后在调用它的析构函数时清理它,就像这个玩具 python 示例:
class Writer():
def __init__(self, filename):
self.f = open(filename, 'w+')
def __del__(self):
self.f.close()
def write(value):
self.f.write(value)
不幸的是,golang 中没有析构函数。除了这样的事情,我不确定我会怎么做:
type ResourceManager interface {
DoWork()
// Close() ?
}
type resourceManager struct {
db *sql.DB
}
func NewResourceManager(db *sql.DB) ResourceManager {
return &resourceManager{db}
}
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
am.Close() // using method shortening
但这似乎不太透明,而且我不确定如何传达 ResourceManager 现在也需要 Close()。我发现这是一个常见的绊脚石,即我还想拥有一个持有 gRPC 客户端连接的资源管理器,如果这些类型的资源不是由资源管理对象管理的,那么我的主要功能似乎是被大量的资源管理所困扰,即打开和关闭。例如,我可以想象这样一种情况,我不想 main
知道有关该对象及其资源的任何信息:
...
func NewResourceManager() ResourceManager {
db, err := sql.Open("mysql", "role@/test_db")
return &resourceManager{db}
}
...
// main elsewhere
am := NewResourceManager()
am.DoWork()
你选择了一个错误的例子,因为你通常会重复使用一个数据库连接,而不是每次使用都打开和关闭一个。因此,您可以将数据库连接传递给使用它的函数,并在调用者中进行资源管理,而无需资源管理器:
// Imports etc omitted for the sake of readability
func PingHandler(db *sql.DB) http.Handler (
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := db.ping(); err != nil {
http.Error(w,e.Error(),500)
}
})
)
func main(){
db,_ := sql.Open("superdb",os.Getenv("APP_DBURL"))
// Note the db connection will only be closed if main exits.
defer db.Close()
// Setup the server
http.Handle("/ping", PingHandler(db))
server := &http.Server{Addr: ":8080"}
// Create a channel for listening on SIGINT, -TERM and -QUIT
stop := make(chan os.Signal, 1)
// Register channel to be notified on said signals
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func(){
// When we get the signal...
<- stop
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
// ... we gracefully shut down the server.
// That ensures that no new connections, which potentially
// would use our db connection, are accepted.
if err := server.Shutdown(ctx); err != nil {
// handle err
}
}
// This blocks until the server is shut down.
// AFTER it is shut down, main exits, the deferred calls are executed.
// In this case, the database connection is closed.
// And it is closed only after the last handler call which uses the connection is finished.
// Mission accomplished.
server.ListenAndServe()
}
所以在这个例子中,不需要资源管理器,老实说,我想不出一个例子实际上需要一个。在极少数情况下,我需要类似的东西,我使用 sync.Pool
.
但是,对于 gRPC 客户端连接,there is no need to maintain a pool:
[...]However, the ClientConn should manage the connections itself, so if a connection is broken, it will reconnect automatically. And if you have multiple backends, it's possible to connect to multiple of them and load balance between them. [...]
所以同样的原则适用:创建一个连接(池),根据需要传递它,确保它在所有工作完成后关闭。
围棋谚语:
– Robert Pike
与其将资源管理隐藏在其他地方,不如尽可能明确地在使用资源的代码附近管理资源。
在 golang 中管理资源所有权的正确方法是什么?假设我有以下内容:
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
db.Close()
总是让调用函数保持所有权和关闭资源的责任是典型的吗?这对我来说感觉有点奇怪,因为关闭后,am
仍然保留一个引用,如果我或其他人以后不小心,可以尝试使用 db
(我想这是延迟的情况;但是,如果我想从这个块返回 ResourceManager am
,我怎么才能正确地推迟文件的关闭呢?我实际上希望它在这个块完成执行时保持打开状态)。我发现在其他语言中,我经常希望允许实例管理资源,然后在调用它的析构函数时清理它,就像这个玩具 python 示例:
class Writer():
def __init__(self, filename):
self.f = open(filename, 'w+')
def __del__(self):
self.f.close()
def write(value):
self.f.write(value)
不幸的是,golang 中没有析构函数。除了这样的事情,我不确定我会怎么做:
type ResourceManager interface {
DoWork()
// Close() ?
}
type resourceManager struct {
db *sql.DB
}
func NewResourceManager(db *sql.DB) ResourceManager {
return &resourceManager{db}
}
db, err := sql.Open("mysql", "role@/test_db")
am := NewResourceManager(db)
am.DoWork()
am.Close() // using method shortening
但这似乎不太透明,而且我不确定如何传达 ResourceManager 现在也需要 Close()。我发现这是一个常见的绊脚石,即我还想拥有一个持有 gRPC 客户端连接的资源管理器,如果这些类型的资源不是由资源管理对象管理的,那么我的主要功能似乎是被大量的资源管理所困扰,即打开和关闭。例如,我可以想象这样一种情况,我不想 main
知道有关该对象及其资源的任何信息:
...
func NewResourceManager() ResourceManager {
db, err := sql.Open("mysql", "role@/test_db")
return &resourceManager{db}
}
...
// main elsewhere
am := NewResourceManager()
am.DoWork()
你选择了一个错误的例子,因为你通常会重复使用一个数据库连接,而不是每次使用都打开和关闭一个。因此,您可以将数据库连接传递给使用它的函数,并在调用者中进行资源管理,而无需资源管理器:
// Imports etc omitted for the sake of readability
func PingHandler(db *sql.DB) http.Handler (
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := db.ping(); err != nil {
http.Error(w,e.Error(),500)
}
})
)
func main(){
db,_ := sql.Open("superdb",os.Getenv("APP_DBURL"))
// Note the db connection will only be closed if main exits.
defer db.Close()
// Setup the server
http.Handle("/ping", PingHandler(db))
server := &http.Server{Addr: ":8080"}
// Create a channel for listening on SIGINT, -TERM and -QUIT
stop := make(chan os.Signal, 1)
// Register channel to be notified on said signals
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func(){
// When we get the signal...
<- stop
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
// ... we gracefully shut down the server.
// That ensures that no new connections, which potentially
// would use our db connection, are accepted.
if err := server.Shutdown(ctx); err != nil {
// handle err
}
}
// This blocks until the server is shut down.
// AFTER it is shut down, main exits, the deferred calls are executed.
// In this case, the database connection is closed.
// And it is closed only after the last handler call which uses the connection is finished.
// Mission accomplished.
server.ListenAndServe()
}
所以在这个例子中,不需要资源管理器,老实说,我想不出一个例子实际上需要一个。在极少数情况下,我需要类似的东西,我使用 sync.Pool
.
但是,对于 gRPC 客户端连接,there is no need to maintain a pool:
[...]However, the ClientConn should manage the connections itself, so if a connection is broken, it will reconnect automatically. And if you have multiple backends, it's possible to connect to multiple of them and load balance between them. [...]
所以同样的原则适用:创建一个连接(池),根据需要传递它,确保它在所有工作完成后关闭。
围棋谚语:
– Robert Pike
与其将资源管理隐藏在其他地方,不如尽可能明确地在使用资源的代码附近管理资源。