模拟多个对SQLite数据库的请求作为goroutine会导致随机的panic错误。

huangapple go评论120阅读模式
英文:

emulating multiple requests to sqlite database as goroutine causes random panic

问题

问题出在并发访问数据库时,可能会导致空指针解引用错误。在你的代码中,你使用了goroutine来执行数据库操作,但是在并发执行时,可能会导致数据库连接(db)在某些goroutine中为nil。这可能是由于并发访问数据库连接而引起的竞态条件。

为了解决这个问题,你可以在每个goroutine中创建一个新的数据库连接,而不是共享同一个连接。这样每个goroutine都有自己的数据库连接,就不会出现竞态条件。

另外,你还可以使用互斥锁(mutex)来保护对数据库连接的访问,以确保同一时间只有一个goroutine可以访问数据库连接。

以下是修改后的代码示例:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "code.google.com/p/go-sqlite/go1/sqlite3"
  6. "crypto/rand"
  7. "encoding/base64"
  8. "sync"
  9. )
  10. func getrandomtext() string {
  11. b := make([]byte, 12)
  12. rand.Read(b)
  13. en := base64.StdEncoding // or URLEncoding
  14. enclen := en.EncodedLen(len(b))
  15. d := make([]byte, enclen)
  16. en.Encode(d, b)
  17. returntext := string(d[:enclen])
  18. return returntext
  19. }
  20. func main() {
  21. dbname := "multitasking.db"
  22. tablename := "multiwrite"
  23. defer time.Sleep(5000 * time.Millisecond)
  24. db, err := sqlite3.Open("file:"+dbname+"?file:locked.sqlite?cache=shared&mode=rwc")
  25. defer db.Close()
  26. if err != nil {
  27. fmt.Printf("failed to open database, error: " + err.Error() + "\n")
  28. return
  29. }
  30. err = db.Exec("DROP TABLE IF EXISTS "+tablename+";")
  31. if err != nil {
  32. fmt.Printf("error dropping table "+tablename+": "+err.Error()+"\n")
  33. }
  34. err = db.Exec("CREATE TABLE "+tablename+" (id INTEGER PRIMARY KEY AUTOINCREMENT, text VARCHAR(200));")
  35. if err != nil {
  36. fmt.Printf("error creating table "+tablename+": "+err.Error()+"\n")
  37. return
  38. } else {
  39. fmt.Printf("successfully created table "+tablename+"!\n")
  40. }
  41. var insertcount int = 128
  42. fmt.Printf("inserting %d random text rows ...\n", insertcount)
  43. var wg sync.WaitGroup
  44. var mu sync.Mutex
  45. for counter := 0; counter < insertcount; counter++ {
  46. wg.Add(1)
  47. go func(count int) {
  48. defer wg.Done()
  49. mu.Lock()
  50. defer mu.Unlock()
  51. newdb, err := sqlite3.Open("file:"+dbname+"?file:locked.sqlite?cache=shared&mode=rwc")
  52. if err != nil {
  53. fmt.Printf("failed to open database, error: " + err.Error() + "\n")
  54. return
  55. }
  56. defer newdb.Close()
  57. if newdb.Exec("INSERT INTO "+tablename+"(text) VALUES('"+getrandomtext()+"');") != nil {
  58. fmt.Printf(" -%d", count)
  59. } else {
  60. fmt.Printf(" +%d", count)
  61. }
  62. }(counter)
  63. }
  64. wg.Wait()
  65. fmt.Printf("\nExecuted! Waiting some seconds...\n")
  66. time.Sleep(3000 * time.Millisecond)
  67. fmt.Printf("\nRequesting...\n")
  68. ReadTable, err := db.Prepare("SELECT id, text FROM "+tablename+";")
  69. err = ReadTable.Query()
  70. if err != nil {
  71. fmt.Printf("failed to read '"+tablename+"' table, error: " + err.Error() + "\n")
  72. return
  73. }
  74. Readloop:
  75. var RowId int
  76. var RowText string
  77. err = ReadTable.Scan(&RowId, &RowText)
  78. if err == nil {
  79. fmt.Printf("> %d | %s\n", RowId, RowText)
  80. ReadTable.Next()
  81. goto Readloop
  82. }
  83. fmt.Printf("Sqlite3 test done! :)\n")
  84. }

这样修改后的代码会为每个goroutine创建一个新的数据库连接,并使用互斥锁来保护对数据库连接的访问,以避免竞态条件。

英文:

I am trying to emulate multiple requests from http.HandleFunc to function which makes changes to sqlite database. I assume that function which is called by http.HandleFunc is actually goroutine. See code below:

  1. package main
  2. import &quot;fmt&quot;
  3. import &quot;time&quot;
  4. import &quot;code.google.com/p/go-sqlite/go1/sqlite3&quot;
  5. import &quot;crypto/rand&quot;
  6. import &quot;encoding/base64&quot;
  7. func getrandomtext() (string) {
  8. b := make([]byte, 12)
  9. rand.Read(b)
  10. en := base64.StdEncoding // or URLEncoding
  11. enclen := en.EncodedLen(len(b))
  12. d := make([]byte, enclen)
  13. en.Encode(d, b)
  14. returntext := string(d[:enclen])
  15. //fmt.Printf(&quot;getrandomtext() : &#39;&quot;+returntext+&quot;&#39;\n&quot;)
  16. return returntext
  17. }
  18. func main() {
  19. dbname := &quot;multitasking.db&quot;
  20. tablename := &quot;multiwrite&quot;
  21. defer time.Sleep(5000 * time.Millisecond)
  22. db, err := sqlite3.Open(&quot;file:&quot;+dbname+&quot;?file:locked.sqlite?cache=shared&amp;mode=rwc&quot;)
  23. defer db.Close()
  24. if err != nil {
  25. fmt.Printf(&quot;failed to open database, error: &quot; + err.Error() + &quot;\n&quot;)
  26. return
  27. }
  28. err = db.Exec(&quot;DROP TABLE IF EXISTS &quot;+tablename+&quot;;&quot;)
  29. if err != nil {
  30. fmt.Printf(&quot;error dropping table &quot;+tablename+&quot;: &quot;+err.Error()+&quot;\n&quot;)
  31. }
  32. err = db.Exec(&quot;CREATE TABLE &quot;+tablename+&quot; (id INTEGER PRIMARY KEY AUTOINCREMENT, text VARCHAR(200));&quot;)
  33. if err != nil {
  34. fmt.Printf(&quot;error creating table &quot;+tablename+&quot;: &quot;+err.Error()+&quot;\n&quot;)
  35. return
  36. } else {
  37. fmt.Printf(&quot;successfully created table &quot;+tablename+&quot;!\n&quot;)
  38. }
  39. var insertcount int = 128
  40. fmt.Printf(&quot;inserting %d random text rows ...\n&quot;, insertcount)
  41. var counter int = 0
  42. insertloop:
  43. if counter &lt; insertcount {
  44. counter++
  45. go func(count int) {
  46. if db.Exec(&quot;INSERT INTO &quot;+tablename+&quot;(text) VALUES(\&quot;&quot;+getrandomtext()+&quot;\&quot;);&quot;) !=nil {
  47. fmt.Printf(&quot; -%d&quot;, count)
  48. } else {
  49. fmt.Printf(&quot; +%d&quot;, count)
  50. }
  51. }(counter)
  52. goto insertloop
  53. }
  54. fmt.Printf(&quot;\nExecuted! Waiting some seconds...\n&quot;)
  55. time.Sleep(3000 * time.Millisecond)
  56. fmt.Printf(&quot;\nRequesting...\n&quot;)
  57. ReadTable, err := db.Prepare(&quot;SELECT id, text FROM &quot;+tablename+&quot;;&quot;)
  58. err = ReadTable.Query()
  59. if err != nil {
  60. fmt.Printf(&quot;failed to read &#39;&quot;+tablename+&quot;&#39; table, error: &quot; + err.Error() + &quot;\n&quot;)
  61. return
  62. }
  63. Readloop:
  64. var RowId int
  65. var RowText string
  66. err = ReadTable.Scan(&amp;RowId, &amp;RowText)
  67. if err == nil {
  68. fmt.Printf(&quot;&gt; %d | %s\n&quot;, RowId, RowText)
  69. ReadTable.Next()
  70. goto Readloop
  71. }
  72. fmt.Printf(&quot;Sqlite3 test done! :)\n&quot;)
  73. }

When multitasking.db does not exists all works fine:

  1. C:\Documents and Settings\JekabsR&gt;multitaskingdb
  2. successfully created table multiwrite!
  3. inserting 128 random text rows ...
  4. Executed! Waiting some seconds...
  5. +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68
  6. +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88
  7. +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +117 +118 +119 +120 +121 +122 +123 +124
  8. +125 +126 +127 +128 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26
  9. +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46
  10. +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +99 +100 +101 +102 +103 +104 +105 +106
  11. +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +1
  12. Requesting...
  13. &gt; 1 | ScnK0DScszFPtNgY
  14. &gt; 2 | 8ALa+Dyk48PpJ4em
  15. &gt; 3 | hmEF4yINhg9SxlNy
  16. ...
  17. &gt; 127 | AAAAwNCvV/wd0/MR
  18. &gt; 128 | SEbPfK/XuVfgnxPj
  19. Sqlite3 test done! :)

When i hit multitaskingdb again, this causes panic:

  1. C:\Documents and Settings\JekabsR&gt;multitaskingdb
  2. successfully created table multiwrite!
  3. inserting 128 random text rows ...
  4. Executed! Waiting some seconds...
  5. +2 +3 +4 +5 +6 +7 +8 +9 +10panic: invalid memory address or nil pointer derefer
  6. ence
  7. fatal error: panic during malloc
  8. [signal 0xc0000005 code=0x0 addr=0x20 pc=0x41b60a]
  9. runtime stack:
  10. runtime.panic(panic: invalid memory address or nil pointer dereference
  11. +11 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -700x520a80, 0x673aaf)
  12. C:/Program Files/Go/src/pkg/runtime/panic.c:233 +0x2b
  13. invalid spdelta 363589 -1
  14. runtime: unexpected return pc for -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -
  15. 82 -83 -84 -85 -86 -87 -88balance called from 0x200
  16. goroutine 48 [syscall]:
  17. runtime.cgocall(0x492817, 0x314476e8)
  18. C:/Program Files/Go/src/pkg/runtime/cgocall.c -89 -90 -91 -92 -93 -94 -9
  19. 5 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106:149 +0x10c fp=0x314476dc
  20. code.google.com/p/go-sqlite/go1/sqlite3._Cfunc_sqlite3_exec(0x3d4258, 0x10f47480
  21. , 0x0, 0x0, 0x0, ...)
  22. C:/DOCUME~1/JekabsR/LOCALS~1/Temp/go-build368528647/code.google.com/p/go
  23. -sqlite/go1/sqlite3/_obj/_cgo_defun.c:456 +0x33 fp=0x314476e8
  24. code.google.com/p/go-sqlite/go1/sqlite3.(*Conn).exec(0x10f2d6c0, 0x10f47480, 0x3
  25. 8 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29, 0x52f578)
  26. C:/ProgramFiles/Go/src/pkg/code.google.com/p/go-sqlite/go1/sqlite3/sqlit
  27. e3.go:545 +0x4c fp=0x31447704
  28. code.google.com/p/go-sqlite/go1/sqlite3.(*Conn).Exec(0x10f2d6c0, 0x10f47480, +3
  29. 0 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +47 -48 -490x39, 0x0,
  30. 0x0, ...)
  31. C:/ProgramFiles/Go/src/pkg/code.google.com/p/go-sqlite/go1/sqlite3/sqlit
  32. e3.go:231 +0xd2 fp=0x31447764
  33. main.func┬Ę001( -50 -51 -52 -53 -54 -55 -56 +1 -107 -108 -109 -110 -111 -112 -11
  34. 3 -114 -115 -1160x2d)
  35. C:/Program Files/Go/src/pkg/development/multitaskingdb/multitaskingdb.go
  36. :52 +0xa2 fp=0x314477c4
  37. runtime.goexit()
  38. C:/Program Files/Go/src/pkg/runtime/proc.c:1394 -117 -118 -119 -120 -121
  39. -122 -123 -124 -125 -126 -127 -128 +12 fp=0x314477c8
  40. created by main.main
  41. C:/Program Files/Go/src/pkg/development/multitaskingdb/multitaskingdb.go
  42. :57 +0x417
  43. goroutine 1 [sleep]:
  44. time.Sleep(0xb2d05e00, 0x0)
  45. C:/Program Files/Go/src/pkg/runtime/time.goc:31 +0x3b
  46. main.main()
  47. C:/Program Files/Go/src/pkg/development/multitaskingdb/multitaskingdb.go
  48. :61 +0x453
  49. goroutine 3 [syscall]:
  50. runtime.goexit()
  51. C:/Program Files/Go/src/pkg/runtime/proc.c:1394
  52. goroutine 49 [syscall]:
  53. code.google.com/p/go-sqlite/go1/sqlite3._Cfunc_sqlite3_exec(0x3d4258, 0x10f47500
  54. , 0x0, 0x0, 0x0, ...)
  55. C:/DOCUME~1/JekabsR/LOCALS~1/Temp/go-build368528647/code.google.com/p/go
  56. -sqlite/go1/sqlite3/_obj/_cgo_defun.c:456 +0x33
  57. code.google.com/p/go-sqlite/go1/sqlite3.(*Conn).exec(0x10f2d6c0, 0x10f47500, 0x3
  58. 8, 0x52f578)
  59. C:/ProgramFiles/Go/src/pkg/code.google.com/p/go-sqlite/go1/sqlite3/sqlit
  60. e3.go:545 +0x4c
  61. code.google.com/p/go-sqlite/go1/sqlite3.(*Conn).Exec(0x10f2d6c0, 0x10f47500, 0x3
  62. 9, 0x0, 0x0, ...)
  63. C:/ProgramFiles/Go/src/pkg/code.google.com/p/go-sqlite/go1/sqlite3/sqlit
  64. e3.go:231 +0xd2
  65. main.func┬Ę001(0x2e)
  66. C:/Program Files/Go/src/pkg/development/multitaskingdb/multitaskingdb.go
  67. :52 +0xa2
  68. created by main.main
  69. C:/Program Files/Go/src/pkg/development/multitaskingdb/multitaskingdb.go
  70. :57 +0x417
  71. fatal error: panic during malloc
  72. [signal 0xc0000005 code=0x0 addr=0x20 pc=0x41b60a]
  73. runtime stack:
  74. runtime.panic(0x520a80, 0x673aaf)
  75. C:/Program Files/Go/src/pkg/runtime/panic.c:233 +0x2b
  76. invalid spdelta 364736 -1
  77. runtime: unexpected return pc for sqlite3BtreeInsert called from 0x1500

Where is the problem?

答案1

得分: 3

根据sqlite3包的文档,每个goroutine应该有一个连接,而不是多个goroutine共享一个连接。

根据https://godoc.org/code.google.com/p/go-sqlite/go1/sqlite3的说明:
并发性
一个单独的连接实例及其所有派生对象(如预编译语句、备份操作等)在没有外部同步的情况下不能从多个goroutine并发使用。唯一的例外是Conn.Interrupt(),可以从另一个goroutine中调用它来中断长时间运行的操作。即使访问同一个数据库文件,同时使用单独的连接实例也是安全的。例如:
// 错误的示例(没有额外的同步)
c, _ := sqlite3.Open("sqlite.db")
go use(c)
go use(c)

// 正确的示例
c1, _ := sqlite3.Open("sqlite.db")
c2, _ := sqlite3.Open("sqlite.db")
go use(c1)
go use(c2)

英文:

According to the documentation of the sqlite3 package, you should have one connection per goroutine and not a single connection shared by several goroutines.

From https://godoc.org/code.google.com/p/go-sqlite/go1/sqlite3:
> Concurrency
>
> A single connection instance and all of its derived
> objects (prepared statements, backup operations, etc.) may NOT be used
> concurrently from multiple goroutines without external
> synchronization. The only exception is Conn.Interrupt(), which may be
> called from another goroutine to abort a long-running operation. It is
> safe to use separate connection instances concurrently, even if they
> are accessing the same database file. For example:
>
> // ERROR (without any extra synchronization)
> c, _ := sqlite3.Open("sqlite.db")
> go use(c)
> go use(c)
>
> // OK
> c1, _ := sqlite3.Open("sqlite.db")
> c2, _ := sqlite3.Open("sqlite.db")
> go use(c1)
> go use(c2)

huangapple
  • 本文由 发表于 2014年4月24日 20:03:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/23268356.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定