当前位置: 首页 > 面试题库 >

Goroutines阻止了连接池

万俟穆冉
2023-03-14
问题内容
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
    "sync"
)

func main() {
    db, _ := sql.Open("postgres", fmt.Sprintf("host=%s dbname=%s user=%s sslmode=disable", "localhost", "dbname", "postgres"))
    defer db.Close()

    db.SetMaxOpenConns(15)
    var wg sync.WaitGroup
    for i := 0; i < 15; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            //#1
            rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
            for rows.Next() {
                //#2
                db.Exec("SELECT * FROM reviews LIMIT 1")
            }
        }()
    }

    wg.Wait()
}

查询1打开15个连接,rows.Next()执行后将关闭它们。但是rows.Next()将永远不会执行,因为它包含db.Exec()等待自由连接的内容。

如何解决这个问题呢?


问题答案:

你有一个僵局。在最坏的情况下,您有15个goroutine持有15个数据库连接,而所有这15个goroutine都需要一个新的连接才能继续。但是要获得新的连接,就必须前进并释放一个连接:死锁。

链接的维基百科文章详细介绍了防止死锁的方法。例如,代码执行仅在拥有需要(或将要使用)的所有资源时才进入关键部分(锁定资源)。在这种情况下,这意味着您将必须保留2个连接(恰好2个;如果只有1个可用,请保留并等待),如果您有2个,则仅继续进行查询。但是在Go中,您无法提前保留连接。在执行查询时,将根据需要分配它们。

通常应避免这种模式。您不应该编写首先保留(有限)资源(在这种情况下为db连接)的代码,然后再释放它,它需要另一个资源。

一个简单的解决方法是执行第一个查询,将其结果保存(例如保存到Go切片中),完成后再进行后续查询(但也不要忘记先关闭sql.Rows)。这样,您的代码不需要同时连接2个连接。

并且不要忘记处理错误!为了简洁起见,我省略了它们,但是您不应在代码中使用它们。

它看起来像这样:

go func() {
    defer wg.Done()

    rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
    var data []int // Use whatever type describes data you query
    for rows.Next() {
        var something int
        rows.Scan(&something)
        data = append(data, something)
    }
    rows.Close()

    for _, v := range data {
        // You may use v as a query parameter if needed
        db.Exec("SELECT * FROM reviews LIMIT 1")
    }
}()

请注意,rows.Close()应将其作为defer语句执行,以确保它将被执行(即使出现紧急情况)。但是,如果仅使用defer rows.Close(),则仅在执行后续查询后才执行,因此不会防止死锁。因此,我将其重构为在另一个可以使用的函数(可能是匿名函数)中调用它defer

    rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
    var data []int // Use whatever type describes data you query
    func() {
        defer rows.Close()
        for rows.Next() {
            var something int
            rows.Scan(&something)
            data = append(data, something)
        }
    }()

还要注意,在第二个for循环中,由捕获的prepared语句(sql.StmtDB.Prepare()可能是执行多次相同(参数化)查询的更好选择。

另一个选择是在新的goroutine中启动后续查询,以便在释放当前锁定的连接(或任何其他goroutine锁定的任何其他连接)时发生在其中执行的查询,但是如果没有显式同步,您将无法控制何时他们被处决了。它可能看起来像这样:

go func() {
    defer wg.Done()

    rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
    defer rows.Close()
    for rows.Next() {
        var something int
        rows.Scan(&something)
        // Pass something if needed
        go db.Exec("SELECT * FROM reviews LIMIT 1")
    }
}()

为了使您的程序也等待这些goroutine,请使用WaitGroup已经生效的:

        // Pass something if needed
        wg.Add(1)
        go func() {
            defer wg.Done()
            db.Exec("SELECT * FROM reviews LIMIT 1")
        }()


 类似资料:
  • 问题内容: 我试图了解Go中的并发性。特别是,我编写了以下线程不安全程序: 我认识到我应该使用渠道来防止与发生竞争,但这不是重点。程序打印,然后似乎永远循环(不再打印任何内容)。我希望它能打印出无限的数字列表,可能由于竞态条件而跳过某些数字并重复其他数字(或更糟的是,在更新数字时打印数字)。 我的问题是:为什么程序只打印一行? 只是要清楚一点:对于这个玩具示例,我不是故意使用渠道。 问题答案: 关

  • 我已经为一个服务实现了socket.io,然后删除了它。但是,我仍然有客户机向endpoint/socket.io发出初始连接请求,而endpoint/socket.io已经不存在了。使用各种http状态代码进行响应似乎并不重要。 是否有方法阻止客户端尝试连接?我每分钟收到几千个请求。

  • 问题内容: 如您所见,我在数据库连接上遇到问题。它给了我这个错误: …由于许多连接错误而被阻止 我搜索了一些答案,但无法解决问题。 我不知道我是否提供了您需要的所有信息,所以如果您需要其他信息,请告诉我。我有来自不同计算机的数据库连接,并且创建了一个用户来访问该数据库,但该用户位于主机行中,因此我想使用IP地址更改它以解决安全问题,因此出现了此错误,因此我卡住。 问题答案: MySQL会阻止连接时

  • 我正在使用Phonegap-CLI(V5.4.0)和Phonegap-developer-app(Android和iOS,都是最新的)创建一个简单的跨平台应用程序。在我的应用程序中,我试图调用我实现的一个REST接口,我使用jQuery AJAX来实现这一点。不幸的是,这些电话似乎不起作用,我没有看到任何来电在我的控制台。(REST接口在我的机器上本地运行。)但是,我知道我的REST接口工作得很好

  • 问题内容: 我正在尝试使用RXTX库来阻止Windows(XP和7)上的串行通信。我已经在两端测试了与Hyperterminal的连接,并且可以正常工作。 我使用以下代码设置了连接:(为清楚起见,省略了异常处理和防御检查) 当我使用 该消息在另一端收到很好,但是打电话 直接返回“ java.io.IOException:基础输入流返回零字节”。 然后,我决定尝试实现自己的阻塞读取逻辑,并写成这样:

  • java.util.concurrent.CompletionException:Akka.Pattern.AskTimeoutException:收件人[Actor[akka:/web_server/user/MyActor#-769383443]]已终止。发送者[null]发送了类型为“com.data.model.request”的消息。 所以我重写了方法,在那里添加了一个log语句。 现在