std::async 启动查询线程时,应每个线程独占一个数据库连接,在 async 内部打开、使用、关闭;避免共享 sqlite3* 句柄,防止随机崩溃;结果传递推荐 future.get() 同步获取,若需实时推送则用线程安全队列;务必 raii 封装连接以防泄漏。

用 std::async 启动查询线程,但别直接传 std::shared_ptr<sqlite3></sqlite3> 进去
SQLite 的 C 接口不是线程安全的,除非编译时定义了 SQLITE_THREADSAFE=1(默认是 1,但运行时仍需按连接隔离使用)。常见错误是把同一个 sqlite3* 句柄传给多个线程并发执行 sqlite3_exec 或 sqlite3_step,结果随机崩溃或返回 SQLITE_BUSY。
正确做法是每个线程独占一个数据库连接:在 std::async 内部打开新连接、查完即关。例如:
auto future = std::async(std::launch::async, [] {
sqlite3* db;
if (sqlite3_open("data.db", &db) != SQLITE_OK) return std::vector<int>{};
// ... 查询逻辑
sqlite3_close(db);
return result_vec;
});
</int>
处理结果时避免在回调里访问 UI 或全局容器而没加锁
异步查完后,你大概率要把结果塞进某个 std::vector<record></record> 或通知 GUI 刷新。这时候如果主线程也在读写同一容器,不加同步必然出问题。
别依赖“我只在 async 回调里 push_back,应该没事”——std::vector 的扩容不是原子操作。稳妥方式是:
- 用
std::mutex保护共享容器,且锁粒度尽量小(比如只锁push_back那一行) - 更推荐:让 async 返回完整结果,主线程用
future.get()拿到后再统一处理(适合查询量不大、能接受短暂停顿的场景) - 若必须实时推送,考虑
std::queue+std::condition_variable实现线程安全队列,避免锁住整个容器生命周期
别用 std::thread 手动管理生命周期,尤其忘了 join() 或 detach()
有人写 std::thread{[&] { /* 查询 */ }}; 就结束,这是未定义行为:临时 std::thread 对象析构时若仍在运行,程序直接终止。更隐蔽的问题是线程函数捕获了局部变量地址(比如 lambda 捕获了栈上 std::string sql),而主线程函数已返回,内存早被复用。
安全底线:
- 用
std::async替代裸std::thread,它自动管理资源和异常传播 - 如果非用
std::thread,必须显式调用join()或detach(),且确保所有捕获变量生命周期覆盖线程全程(比如改用std::shared_ptr包裹数据) - 注意
std::async默认可能延迟执行(std::launch::deferred),加std::launch::async强制立即启新线程
MySQL / PostgreSQL 等客户端库要检查是否线程安全
SQLite 是单连接单线程模型;但 MySQL 的 libmysqlclient 要求调用 mysql_thread_init() 和 mysql_thread_end(),PostgreSQL 的 libpq 则要求每个线程独立调用 PQconnectdb —— 它们都不允许跨线程复用连接句柄。
关键点:
- 查文档确认你用的客户端库是否支持“每线程独立连接”,而不是“多线程共享连接”
- 像
mysql_init(nullptr)返回的句柄不能跨线程传;必须在线程内调用mysql_init+mysql_real_connect - 有些封装库(如
soci)内部做了线程适配,但默认仍建议每线程一个session对象
最常被忽略的是连接泄漏:异步任务抛异常时没走到 sqlite3_close 或 PQfinish。务必用 RAII 封装(比如自定义 DBConnection 类,在析构里关连接),否则跑久了进程会卡死在 too many open files。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











