sqlite3_mutex_enter() 访问冲突。为什么?

2 投票
2 回答
1252 浏览
提问于 2025-04-18 10:00

我在一个Python3/C模块中使用sqlite-amalgamation-3080500。

我的Python模块创建了一些表格,然后通过PyCapsule把sqlite3的句柄返回给Python环境。

所以,在第二个模块中,我尝试使用这个相同的sqlite3句柄来创建更多的表格。但我的程序出错了。我遇到了一个“访问冲突错误”,这个错误是在sqlite3_prepare_v2()调用的sqlite3_mutex_enter()中发生的。

在python.exe中发生第一次异常:0xC0000005:在位置0x00000000执行时发生访问冲突。未处理的异常在python.exe中的0x7531C9F1:0xC0000005:在位置0x00000000执行时发生访问冲突。

这真的线程安全吗?我觉得这样做是可以的。我以前也这样做过,但那时我是在Mac上用XCode。现在我想在MSVC 2013上做同样的事情。

下面是我运行查询的代码:

bool register_run(register_db_t *pReg, const char *query)
{
    int ret, len;
    sqlite3_stmt *stmt;
    const char *err;

    stmt = NULL;

    len = (int)strlen(query);

    ret = sqlite3_prepare_v2(pReg->pDb, query, len, &stmt, NULL);

    if (ret != SQLITE_OK) {

        err = sqlite3_errmsg(pReg->pDb);

        fprintf(stderr, "sqlite3_prepare_v2 error: %s\n%s\n",
                err, query);

        return false;
    }


    ret = register_run_stmt(pReg, query, stmt);

    sqlite3_finalize(stmt);

    return ret;
}

这是我如何导出句柄以便在我的第二个C模块中使用:

// Register's getattro
static PyObject* Register_getattro(RegisterObject *self, PyObject *name)
{
    // ...
    } else if (PyUnicode_CompareWithASCIIString(name, "handle") == 0) {
        register_db_t *handle = self->db;

        return PyCapsule_New(handle, NULL, NULL);
    }

    return PyObject_GenericGetAttr((PyObject *)self, name);
}

这是我在Python中连接各个部分的代码:

import registermodule, loggermodule

reg = registermodule.Register("mydata.db")
loggermodule.set_register(reg.handle)

这是我在第二个模块中如何使用这个句柄:

static PyObject* loggerm_set_register(PyObject *self, PyObject *args)
{
    register_db_t *pReg;
    PyObject *capsule;

    if (!PyArg_ParseTuple(args, "O:set_register", &capsule)) {
        return NULL;
    }

    if (!PyCapsule_CheckExact(capsule)) {
        PyErr_SetString(PyExc_ValueError,
            "The object isn't a valid pointer.");
        return NULL;
    }

    pReg = PyCapsule_GetPointer(capsule, NULL);

    if (!logger_set_register(pReg)) {
        PyErr_SetString(PyExc_SystemError,
            "Could not set the pointer as register.");
        return NULL;
    }

    Py_RETURN_NONE;
}

最后,这是导致出错的例程:

bool logger_set_register(register_db_t *pReg)
{
    char *query = "CREATE TABLE IF NOT EXISTS tab_logger ("
                "date       NUMERIC,"
                "level      TEXT,"
                "file       TEXT,"
                "function   TEXT,"
                "line       INTEGER,"
                "message    TEXT)";
    g_pReg = pReg;

    return register_run(g_pReg, query);
}

还有导致一切出错的sqlite3例程:

SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
  if( p ){
    sqlite3GlobalConfig.mutex.xMutexEnter(p);
  }
}

抱歉提供了这么多代码片段,但我对这个问题毫无头绪。

提前谢谢你们。

2 个回答

0

我不太确定你的问题是否直接和sqlite3_initialize();有关。因为sqlite3_initialize();至少会在sqlite3_open_v2这个函数执行时自动调用一次。

我建议你关注一下在sqlite3_open_v2中缺少了选项SQLITE_OPEN_FULLMUTEX的问题。建议你总是设置这个选项。使用互斥锁的代价非常低,尤其是在考虑到Python带来的额外开销时。在单线程情况下几乎可以忽略不计,而在多线程情况下几乎是必须的。我甚至不明白为什么这个选项还会存在,应该总是启用它。所以,宁可安全一些,也不要后悔。我不知道使用它有什么真正的坏处。SQLite是“轻量级”的。

顺便说一下,sqlite3Config.mutex为NULL是合法的。这个值应该只被SQLite使用来检查这个条件,想了解我说的意思,可以在sqlite3.c中搜索

sqlite3_mutex_enter(db->mutex);

来看看。

4

我不知道为什么,在Windows上,Python的C模块之间的全局变量不一样。在Mac OS上就没有这个问题,尽管我之前有过这样的经验。

在Windows上,Python模块是DLL文件,所以它们之间不共享同一个全局变量堆栈。

我发现sqlite3Config.mutex在我的第二个Python C模块中是NULL,这导致了访问违规错误。但sqlite3Config.mutex是一个全局变量,这个东西应该由之前的模块来初始化。

现在,知道了这一点,我通过调用这个函数解决了问题:

sqlite3_initialize();

现在一切都正常运作了!

撰写回答