多线程连接时MariaDB C++ Connector崩溃错误求助
connect方法导致服务器崩溃 在Ubuntu 20.04系统中使用gcc编译器及MariaDB C++ Connector时,Web服务器负载测试发现,只要存在多个并发连接,服务器就会崩溃。问题定位到MariaDB驱动的connect方法,报错为以下两种异常之一:
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
Aborted (core dumped)terminate called after throwing an instance of 'std::invalid_argument'
what(): stoi
Aborted (core dumped)
最小复现代码(仅依赖标准库与mariadb/conncpp.hpp):
#include <mariadb/conncpp.hpp> #include <iostream> #include <thread> #include <string> sql::Driver* driver = sql::mariadb::get_driver_instance(); sql::SQLString connectionUrl = "jdbc:mariadb://localhost:3306/the_database_name"; sql::Properties properties = {{"user", "the_username"}, {"password", "the_password"}}; int main() { // simulate 2 concurrent connections each making 500 requests std::vector<std::function<void()>> t1cbs; std::vector<std::function<void()>> t2cbs; for(int i = 0; i < 500; i++) // if does not fail for you at 500 up the number until it does { t1cbs.push_back([&]{ std::unique_ptr<sql::Connection> conn(driver->connect(connectionUrl, properties)); std::cout << "t1:" << conn->getHostname().c_str() << std::endl; }); // comment out this block to keep the second thread from executing, and you will see // that no errors occur t2cbs.push_back([&]{ std::unique_ptr<sql::Connection> conn(driver->connect(connectionUrl, properties)); std::cout << "t2:" << conn->getHostname().c_str() << std::endl; }); } std::thread t1([&]{ for(auto& cb : t1cbs) cb(); }); std::thread t2([&]{ for(auto& cb : t2cbs) cb(); }); t1.join(); t2.join(); return 0; }
经排查,driver->connect方法存在线程安全问题,官方文档也提及多线程环境下直接调用可能引发异常,以下是可行的规避方案:
对
connect调用加全局互斥锁
用互斥锁保护所有并发的connect调用,确保同一时间只有一个线程执行连接操作,直接解决线程冲突问题:#include <mutex> std::mutex connect_mutex; // 在每个线程的连接逻辑中加锁 auto conn_func = [&]{ std::lock_guard<std::mutex> lock(connect_mutex); std::unique_ptr<sql::Connection> conn(driver->connect(connectionUrl, properties)); std::cout << "t1:" << conn->getHostname().c_str() << std::endl; };缺点是会牺牲部分并发性能,适合连接频率较低的场景。
每个线程独立获取Driver实例
避免多个线程共享同一个Driver实例,改为每个线程单独获取Driver:// 不在全局定义driver,而是在每个线程的连接逻辑中初始化 t1cbs.push_back([&]{ sql::Driver* local_driver = sql::mariadb::get_driver_instance(); std::unique_ptr<sql::Connection> conn(local_driver->connect(connectionUrl, properties)); std::cout << "t1:" << conn->getHostname().c_str() << std::endl; });每个线程使用独立的Driver实例,消除共享状态带来的线程安全隐患。
使用连接池替代直接创建连接
生产环境优先选择连接池,它不仅能管理连接生命周期、降低频繁创建连接的开销,还会内部处理线程安全问题。MariaDB C++ Connector原生支持连接池:#include <mariadb/conncpp/ConnectionPool.hpp> // 初始化连接池(建议全局或单例模式) sql::ConnectionPool* pool = sql::mariadb::ConnectionPool::create( driver, connectionUrl, properties, 10, 20); // 最小10个连接,最大20个 // 线程中获取连接 auto conn_func = [&]{ std::unique_ptr<sql::Connection> conn(pool->getConnection()); std::cout << "t1:" << conn->getHostname().c_str() << std::endl; // 连接使用后自动归还到池,无需手动关闭 };这是高并发场景下的最优解决方案。
升级MariaDB C++ Connector版本
部分旧版本的Connector存在线程安全bug,升级到最新稳定版可能已修复相关问题。Ubuntu下可通过以下命令升级:sudo apt update && sudo apt install --only-upgrade libmariadb-dev libmariadbcpp-dev
内容的提问来源于stack exchange,提问作者whitwhoa

