Golang中Cgo参数传递Go指针到Go指针的问题

Golang中Cgo参数传递Go指针到Go指针的问题 我的代码:

package main

import (
    _ "github.com/ibmdb/go_ibm_db"
        "fmt"
        "database/sql"
        "math/rand"
        )

func main(){
con:="HOSTNAME=10.45.6.33;PORT=50000;DATABASE=go;UID=*****;PWD=********"
db,err:=sql.Open("go_ibm_db",con)
if err != nil{
fmt.Println(err)

}
defer db.Close()
db.Exec("DROP table byt1")
_,err=db.Query("create table byt1(name INT)")
if err != nil{
    fmt.Println(err)
}

st1,err:=db.Prepare("select name from byt1 ORDER BY name DESC")
if err != nil{
fmt.Println(err)
}

st2,err:=db.Prepare("Insert into byt1(name) values(?)")
if err != nil{
fmt.Println(err)
}

for n := 1; n <= 3; n++ {
    if _, err := st2.Exec(n); err != nil {
        fmt.Println("insert(%d) = %v", n, err)
        }
    }

const nRuns = 10
ch := make(chan bool)
        for i := 0; i < nRuns; i++ {
                go func() {
                        defer func() {
                                ch <- true
                        }()
                        for j := 0; j < 10; j++ {
                                count := 0
                                if err := st1.QueryRow().Scan(&count); err != nil && err != sql.ErrNoRows {
                                        fmt.Println("Query: %v", err)
                                        return
                                }
                                if _, err := st2.Exec(rand.Intn(100)); err != nil {
                                       fmt.Println("Insert: %v", err)
                                        return
                                }
                        }
                }()
        }
        for i := 0; i < nRuns; i++ {
                <-ch
        }

}

错误信息: panic: runtime error: cgo argument has Go pointer to Go pointer

goroutine 16 [running]: github.com/ibmdb/go_ibm_db/api.SQLBindCol.func1(0x4000100010001, 0xc00005a3f8, 0xc000000004, 0xc00005a3d8, 0x4edfe0) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/api/zapi_unix.go:26 +0x4c github.com/ibmdb/go_ibm_db/api.SQLBindCol(0x4000100010001, 0xc00005a3f8, 0x4, 0xc00005a3d8, 0x0) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/api/zapi_unix.go:26 +0x59 github.com/ibmdb/go_ibm_db.(*BufferLen).Bind(0xc00005a3d8, 0x10001, 0x0, 0x440004, 0xc00005a3f8, 0x4, 0x8, 0xc000010350) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/column.go:29 +0x62 github.com/ibmdb/go_ibm_db.(*BindableColumn).Bind(0xc00005a3c0, 0x10001, 0x0, 0xc00005a3c0, 0x0, 0x0) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/column.go:202 +0x6c github.com/ibmdb/go_ibm_db.(*ODBCStmt).BindColumns(0xc000092140, 0x7d9de0, 0x0) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/odbcstmt.go:148 +0x1eb github.com/ibmdb/go_ibm_db.(*Stmt).Query(0xc00007e2d0, 0x7d9de0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) /home/rakhil/go/src/github.com/ibmdb/go_ibm_db/stmt.go:93 +0xe5 database/sql.ctxDriverStmtQuery(0x513040, 0xc000016138, 0x513140, 0xc00007e2d0, 0x7d9de0, 0x0, 0x0, 0x0, 0x0, 0x0, …) /work/rakhil/go/src/database/sql/ctxutil.go:94 +0x16b database/sql.rowsiFromStatement(0x513040, 0xc000016138, 0x512f40, 0xc000010290, 0xc00005a240, 0x0, 0x0, 0x0, 0x0, 0x0, …) /work/rakhil/go/src/database/sql/sql.go:2511 +0x159 database/sql.(*Stmt).QueryContext(0xc0000a6000, 0x513040, 0xc000016138, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) /work/rakhil/go/src/database/sql/sql.go:2463 +0x203 database/sql.(*Stmt).QueryRowContext(0xc0000a6000, 0x513040, 0xc000016138, 0x0, 0x0, 0x0, 0xc000016218) /work/rakhil/go/src/database/sql/sql.go:2521 +0x6d database/sql.(*Stmt).QueryRow(0xc0000a6000, 0x0, 0x0, 0x0, 0x0) /work/rakhil/go/src/database/sql/sql.go:2540 +0x61 main.main.func1(0xc00001c2a0, 0xc0000a6000, 0xc0000a6090) /home/rakhil/go/src/github.com/bradfitz/go-sql-test/src/sqltest/akh.go:49 +0xb4 created by main.main /home/rakhil/go/src/github.com/bradfitz/go-sql-test/src/sqltest/akh.go:43 +0x472 exit status 2

提前感谢。


更多关于Golang中Cgo参数传递Go指针到Go指针的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

我已经修改了,会删除另一个。能解释一下为什么会出现这个错误吗

更多关于Golang中Cgo参数传递Go指针到Go指针的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


select语句的写法有误。

select name from byt1 ORDER BY name DESC

在Windows环境下可以正常运行,但在Linux系统上会出现错误。

我了解到这个错误是因为在主协程中,在所有子协程完成之前就释放了通道。我不知道这是正确还是错误的。如果这是错误,该如何解决。

当我在Linux系统中执行:

export GODEBUG=cgocheck=0

程序运行正常。这样做是否合适,还是我需要采取其他措施?

请不要对同一个问题开设多个主题。同时,请记得正确格式化代码以提高可读性。更多信息请参考这篇文章这篇指南

akhilravuri:

select count from byt1 ORDER BY count DESC

这是该数据库SQL中的有效语句吗?在MySQL中应该是这样的:

select count(*) from byt1 ORDER BY count DESC

用于计算表中的行数,而且它只会返回一个值,所以实际上不需要进行排序。

查阅等待组。这是等待多个Go协程完成的简便方法。类似这样:

func main() {
    // 示例代码
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // 执行某些操作
        }()
    }
    wg.Wait()
}

如果你预先知道协程数量(n),可以调用wg.Add(n);否则每个协程执行wg.Add(1)。每个协程在完成时调用wg.Done()。主协程使用wg.Wait()等待所有协程完成。

这个错误是由于在Cgo调用中传递了包含Go指针到Go指针的参数,违反了Go的Cgo指针传递规则。具体来说,在并发执行数据库查询时,go_ibm_db驱动在Cgo边界传递了非法的指针结构。

问题出现在并发使用st1.QueryRow().Scan(&count)时,其中&count是一个Go指针,在Cgo调用中被传递给了底层的ODBC驱动。根据Go的Cgo规则,不能将指向Go内存的指针传递给C代码,如果这些指针本身包含其他Go指针。

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

package main

import (
    _ "github.com/ibmdb/go_ibm_db"
    "fmt"
    "database/sql"
    "math/rand"
    "sync"
)

func main() {
    con := "HOSTNAME=10.45.6.33;PORT=50000;DATABASE=go;UID=*****;PWD=********"
    db, err := sql.Open("go_ibm_db", con)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer db.Close()
    
    db.Exec("DROP table byt1")
    _, err = db.Exec("create table byt1(name INT)")
    if err != nil {
        fmt.Println(err)
    }

    const nRuns = 10
    var wg sync.WaitGroup
    
    for i := 0; i < nRuns; i++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            
            // 每个goroutine创建自己的语句句柄
            st1, err := db.Prepare("select name from byt1 ORDER BY name DESC")
            if err != nil {
                fmt.Printf("Worker %d prepare st1 error: %v\n", workerID, err)
                return
            }
            defer st1.Close()
            
            st2, err := db.Prepare("Insert into byt1(name) values(?)")
            if err != nil {
                fmt.Printf("Worker %d prepare st2 error: %v\n", workerID, err)
                return
            }
            defer st2.Close()
            
            for j := 0; j < 10; j++ {
                var count int
                err := st1.QueryRow().Scan(&count)
                if err != nil && err != sql.ErrNoRows {
                    fmt.Printf("Worker %d query error: %v\n", workerID, err)
                    return
                }
                
                _, err = st2.Exec(rand.Intn(100))
                if err != nil {
                    fmt.Printf("Worker %d insert error: %v\n", workerID, err)
                    return
                }
            }
        }(i)
    }
    
    wg.Wait()
}

主要修改点:

  1. 为每个goroutine创建独立的预处理语句(st1st2),避免并发访问同一语句对象
  2. 使用sync.WaitGroup替代channel进行goroutine同步
  3. 确保每个语句在使用后正确关闭
  4. 使用db.Exec替代db.Query执行DDL语句

这样修改后,每个goroutine都有自己的数据库语句句柄,避免了在Cgo边界传递共享的Go指针,从而解决了"cgo argument has Go pointer to Go pointer"的错误。

回到顶部