【问题标题】:C++ thread attach/dettach segfaultsC++ 线程附加/分离段错误
【发布时间】:2015-09-11 23:22:17
【问题描述】:

我使用一个用 C++ 编写的插件在 MySQL 上运行查询。它在 Xojo (www.xojo.com) 制作的应用程序中使用。

问题是,如果太多查询过于频繁地执行,它会在 linux 上因分段错误而崩溃。

插件本身的工作原理是在执行查询之前从调用线程中分离出来,以免阻塞主应用程序等,然后在完成后重新连接。我认为重新连接是问题所在(Linux 中的 gdb 调试似乎是这样的)但由于 Xojo 的框架上没有符号,我不太确定。

这是用于分离和重新连接的两种方法/功能

void ReattachCurrentThread(void *token)
{
    static void (*pAttachThread)(void*) = nullptr;
    if (!pAttachThread)
        pAttachThread = (void (*)(void *)) gResolver("_UnsafeAttachCurrentThread");
    if (pAttachThread) pAttachThread( token );
}

void * DetachCurrentThread(void)
{
    static void * (*pDetachThread)(void) = nullptr;
    if (!pDetachThread)
        pDetachThread = (void * (*)(void)) gResolver("_UnsafeDetachCurrentThread");
    if (pDetachThread) return pDetachThread();
    return nullptr;
}

这里有一个地方叫做:

REALdbCursor MySQLPerformSelect(MySQLDatabaseData *db, REALstring queryStr)
{
    if (db->fConnection == nullptr) return nullptr;

    if (!LockDatabaseUsage( db )) return nullptr;

    REALstringData stringData;
    if (!REALGetStringData( queryStr, REALGetStringEncoding( queryStr ), &stringData )) return nullptr;

    void *detachToken = DetachCurrentThread();
    int err = mysql_real_query( db->fConnection, (const char *)stringData.data, stringData.length );
    ReattachCurrentThread( detachToken );
    db->CaptureLastError();

    REALDisposeStringData( &stringData );

    REALdbCursor retCursor = nullptr;
    if (0 == err) {
        // Allocate a cursor
        MySQLCursorData *curs = new MySQLCursorData;
        bzero( curs, sizeof( MySQLCursorData ) );

        curs->fCursor = new MySQLCursor( db );

        retCursor = NewDBCursor( curs );
    }

    UnlockDatabaseUsage( db );

    return retCursor;
}

我的问题是:上面的代码有什么问题吗?它是否会导致段错误,因为它不小心等等?我不是 C++ 程序员,但在我的理解中似乎太直率,比如不尝试先查看线程是否可用等。再说一次,我不是 C++ 程序员,所以我要说的只是可能是荒谬的等等......

“完整”插件的代码在这里: plugin's source

【问题讨论】:

  • (void * (*)(void)) -- 避免类型检查让我害怕。它有多种方法可以搞砸。
  • 当然,但这不是我的代码,所以我没事。 :-) 段错误似乎与线程优先级有关。发生的情况是调用此插件方法的线程在协作线程方案中具有最低的优先级。所以,我认为当它试图重新连接时,它会“超时”(?),因为线程需要 太长时间 从睡眠中回来等。我已经提高了线程的优先级在调用该方法之前到最高点,并且超过 24 小时没有更多的 sigfaults(它过去每 10-15 分钟发生一次segfault)
  • 尝试“驯服”其他线程可能是明智的。这个问题可能有一天会再次抬头。

标签: c++ mysql linux multithreading plugins


【解决方案1】:

代码至少有2个问题:

  1. ReattachCurrentThread()/DetachCurrentThread() 的调用不同步
  2. UnlockDatabaseUsage() 并不总是被调用:函数MySQLPerformSelect() 可以在不调用UnlockDatabaseUsage() 的情况下返回

第一个问题可以这样解决:

#include <mutex>

std::mutex g_attachmentMutex;
void ReattachCurrentThread(void *token)
{
    std::lock_guard<std::mutex> mlg(g_attachmentMutex);
    static void (*pAttachThread)(void*) = nullptr;
    if (!pAttachThread)
        pAttachThread = (void (*)(void *)) gResolver("_UnsafeAttachCurrentThread");
    if (pAttachThread) pAttachThread( token );
}

void * DetachCurrentThread(void)
{
    std::lock_guard<std::mutex> mlg(g_attachmentMutex);
    static void * (*pDetachThread)(void) = nullptr;
    if (!pDetachThread)
        pDetachThread = (void * (*)(void)) gResolver("_UnsafeDetachCurrentThread");
    if (pDetachThread) return pDetachThread();
    return nullptr;
}

第二个问题可以这样解决:

class MySQLPerformSelectCleaner
{
    MySQLDatabaseData *_db;
public:
    MySQLPerformSelectCleaner(MySQLDatabaseData *db)
        : _db(db)
    {
    }
    ~MySQLPerformSelectCleaner()
    {
        UnlockDatabaseUsage(_db);
    }
};
REALdbCursor MySQLPerformSelect(MySQLDatabaseData *db, REALstring queryStr)
{
    if (db == nullptr || db->fConnection == nullptr) return nullptr;

    if (!LockDatabaseUsage( db )) return nullptr;
    MySQLPerformSelectCleaner c(db);

    REALstringData stringData;
    if (!REALGetStringData( queryStr, 
        REALGetStringEncoding( queryStr ), &stringData )) 
    {
        return nullptr;
    }

    void *detachToken = DetachCurrentThread();
    // perhaps a check for detachToken==nullptr is needed here
    int err = mysql_real_query( db->fConnection, (const char *)stringData.data, stringData.length );
    ReattachCurrentThread( detachToken );
    db->CaptureLastError();

    REALDisposeStringData( &stringData );

    REALdbCursor retCursor = nullptr;
    if (0 == err) {
        // Allocate a cursor
        MySQLCursorData *curs = new MySQLCursorData;
        bzero( curs, sizeof( MySQLCursorData ) );

        curs->fCursor = new MySQLCursor( db );

        retCursor = NewDBCursor( curs );
    }

    // Rely on the cleaner to call this: UnlockDatabaseUsage( db );
    return retCursor;
}

【讨论】:

    【解决方案2】:

    您确定DetachCurrentThread() 总是返回指向令牌的指针吗?添加以下检查,看看是否有帮助:

    void *detachToken = DetachCurrentThread();
    if (detachToken == nullptr) return nullptr; // ensure the pointer to a token is returned before proceeding further.
    

    【讨论】:

    • 是的,它返回调用线程,所以除非调用应用程序在调用方法和此 Detach 调用之间崩溃,否则它应该总是返回一个指针等。我想我可能已经找到了解决方案,但仍然检查...
    【解决方案3】:

    如果代码仅在运行的查询过多时崩溃,您可能需要检查_UnsafeAttachCurrentThread_UnsafeDetachCurrentThread 是否存在可能导致段错误的竞争条件。如果查询太多,DetachCurrentThread 可能会返回 nullptr?您可以添加检查 detachToken 在这种情况下是否有效。

    此外,如果REALGetStringData 失败,可能会出现死锁。返回前请致电UnlockDatabaseUsage

    【讨论】:

      【解决方案4】:

      我认为你的问题就在这里:

      if (db-&gt;fConnection == nullptr) return nullptr;

      变量db 是一个指针。在评估此条件之前,您需要进行检查:

      if (!db) 
      {
          // throw error, return from function call, etc.
      }
      

      【讨论】:

        猜你喜欢
        • 2022-01-10
        • 2016-02-09
        • 2012-05-10
        • 2015-09-10
        • 2014-01-10
        • 1970-01-01
        • 1970-01-01
        • 2020-02-29
        • 2011-06-20
        相关资源
        最近更新 更多