To, co máte, je deadlock . V nejhorším případě máte 15 goroutin držících 15 databázových připojení a všech těchto 15 goroutin vyžaduje pro pokračování nové připojení. Ale aby bylo možné získat nové připojení, bylo by nutné pokročit a uvolnit připojení:uváznutí.
Odkazovaný článek na wikipedii podrobně popisuje prevenci uváznutí. Například spuštění kódu by mělo vstoupit do kritické sekce (která zamyká prostředky), když má všechny prostředky, které potřebuje (nebo bude potřebovat). V tomto případě to znamená, že byste si museli rezervovat 2 připojení (přesně 2; pokud je volné pouze 1, nechte to a počkejte), a pokud máte tato 2, teprve potom pokračujte v dotazech. V Go si ale nemůžete rezervovat spoje předem. Jsou přidělovány podle potřeby při provádění dotazů.
Obecně je třeba se tomuto vzoru vyhnout. Neměli byste psát kód, který nejprve rezervuje (konečný) zdroj (v tomto případě připojení db), a než jej uvolní, požaduje další.
Snadným řešením je provést první dotaz, uložit jeho výsledek (např. do řezu Go) a až s tím budete hotovi, pokračovat v následujících dotazech (ale také nezapomeňte zavřít sql.Rows
První). Tímto způsobem váš kód nepotřebuje 2 připojení současně.
A nezapomeňte řešit chyby! Pro stručnost jsem je vynechal, ale neměli byste je ve svém kódu používat.
Takhle by to mohlo vypadat:
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")
}
}()
Všimněte si, že rows.Close()
by měl být proveden jako defer
příkaz, abyste se ujistili, že bude proveden (i v případě paniky). Ale pokud jednoduše použijete defer rows.Close()
, který by byl proveden až po provedení následujících dotazů, takže to nezabrání uváznutí. Takže bych to přefaktoroval tak, aby to zavolalo v jiné funkci (což může být anonymní funkce), ve které můžete použít 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)
}
}()
Všimněte si také, že ve druhém for
smyčka připraveného příkazu (sql.Stmt
) získal DB.Prepare()
pravděpodobně by bylo mnohem lepší volbou provést stejný (parametrizovaný) dotaz vícekrát.
Další možností je spouštět následné dotazy v nových goroutinech, takže dotaz spuštěný v nich se může stát, když je uvolněno aktuálně uzamčené připojení (nebo jakékoli jiné připojení uzamčené jakoukoli jinou goroutine), ale pak bez explicitní synchronizace nemáte kontrolu, kdy jsou popraveni. Mohlo by to vypadat takto:
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")
}
}()
Chcete-li, aby váš program čekal i na tyto gorutiny, použijte WaitGroup
již máte v akci:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()