为了账号安全,请及时绑定邮箱和手机立即绑定

golang 资源所有权模式(文件、连接、关闭)

golang 资源所有权模式(文件、连接、关闭)

Go
不负相思意 2023-08-07 14:56:25
在 golang 中管理资源所有权的正确方法是什么?假设我有以下内容:db, err := sql.Open("mysql", "role@/test_db")am := NewResourceManager(db)am.DoWork()db.Close()总是让调用函数维护关闭资源的所有权和责任是典型的吗?这对我来说有点奇怪,因为关闭后,仍然保留引用,如果我或其他人稍后不小心,am可以尝试使用(我想这是延迟的情况;但是,如果我想将 ResourceManager 传回)从这个块开始,我如何正确推迟文件的关闭?我实际上希望它在这个块完成执行时保持打开状态)。我发现在其他语言中,我经常希望允许实例管理资源,然后在调用析构函数时清理它,就像这个玩具 python 示例:dbamclass Writer():   def __init__(self, filename):       self.f = open(filename, 'w+')   def __del__(self):       self.f.close()   def write(value):       self.f.write(value)不幸的是,golang 中没有析构函数。除了这样的事情之外,我不确定如何在 go 中做到这一点: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 elsewheream := NewResourceManager()am.DoWork()
查看完整描述

1 回答

?
温温酱

TA贡献1752条经验 获得超4个赞

您选择了一个不好的示例,因为您通常会重用数据库连接,而不是每次使用时打开和关闭一个数据库连接。因此,您可以将数据库连接传递给使用它的函数,并在调用者中进行资源管理,而不需要资源管理器:


// 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 客户端连接,也无需维护池:

[...]但是,ClientConn 应该自行管理连接,因此如果连接断开,它将自动重新连接。如果您有多个后端,则可以连接到其中多个后端并在它们之间实现负载平衡。[...]

因此,同样的原则适用:创建一个连接(池),根据需要传递它,确保在完成所有工作后关闭它。

不要将资源管理隐藏在其他地方,而是尽可能靠近使用资源的代码并尽可能明确地管理资源。


查看完整回答
反对 回复 2023-08-07
  • 1 回答
  • 0 关注
  • 104 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信