直接读取pthread互斥锁的所有者字段是否安全?

时间:2017-08-23 14:06:33

标签: c++ linux multithreading pthreads mutex

我对我在Linux C ++应用程序中使用boost :: mutex对象的方式有疑问。我有一组用于执行各种互斥操作的便捷宏,例如我有一些宏可以返回任何线程是否锁定互斥锁,无论是否已经被调用线程专门锁定,还有其他几个。需要知道当前锁定互斥锁的线程(如果有)的宏如下:

// Determine whether or not a boost mutex is already locked
#define IsBoostMutexLocked(m) ((m.native_handle()->__data.__owner) != 0)

// Determine whether or not a boost mutex is already locked by the calling thread
#define IsBoostMutexLockedByCallingThread(m) ((m.native_handle()->__data.__owner) == (syscall(SYS_gettid)))

但我开始怀疑直接读取__owner int字段是否安全。当另一个线程忙于锁定或解锁互斥锁时,一个线程是否可以不尝试读取__owner字段,从而写入__owner字段?

因此,我设计了一个测试,试图暴露任何数据竞争漏洞并在检测到这种情况时中止。到目前为止,我有100个线程同时锁定,解锁和读取一个全局互斥锁的__owner,每个线程进行数千万次循环迭代,而不是曾经读过一个无效的__owner值。下面我已经包含了整个测试代码。坦率地说,我很惊讶我从来没有读过一个糟糕的__owner值。

有人可以向我解释为什么即使其他线程试图锁定/解锁,直接读取互斥锁的__owner(显然)也是安全的吗?提前谢谢!

// The test mutex
boost::mutex g_TestMutex;

// The number of threads to launch for the test
#define NUM_THREADS_TO_LAUNCH 100

// The thread IDs of all test threads
long int g_AllSpecialThreadsTIDs[NUM_THREADS_TO_LAUNCH];

// Whether or not each test thread is ready to begin the test
std::atomic<bool> g_bEachTestThreadIsReadyToBegin[NUM_THREADS_TO_LAUNCH];

// Whether or not the test is ready to begin
std::atomic<bool> g_bTestReadyToBegin(false);

// A structure that encapsulates data to be passed to each test thread
typedef struct {
    long *pStoreTIDLoc; // A pointer to the variable at which to store the thread ID
    std::atomic<bool> *pTIDStoredLoc; // A pointer to the variable at which to store the status of whether or not the thread ID has been set
} TestThreadDataStructure;

// Ensure that a test thread ID is valid
void AssertIsValidTID(int iTID)
{
    // Whether or not this thread ID is valid
    bool bValid = false;

    // If the thread ID indicates that no-one has locked the mutex
    if (iTID == 0)
    {
        // A thread ID indicating that no-one has locked the mutex is always valid
        bValid = true;
    }

    // Or, if this is a non-zero thread ID
    else
    {
        // For each test thread
        for (int i = 0; i < NUM_THREADS_TO_LAUNCH; i++)
        {
            // If this is a thread ID match
            if (iTID == static_cast<int>(g_AllSpecialThreadsTIDs[i]))
            {
                // Set that the incoming thread ID is valid
                bValid = true;

                // Stop looking
                break;
            }
        }
    }

    // If the incoming thread ID is invalid
    if (!bValid)
    {
        // The test has failed
        abort();
    }
}

// Each test thread
void TestMutexTesterThread(void *pArg)
{
    // Each mutex owner thread ID
    int iOwner = 0;

    // Unpack the incoming data structure
    TestThreadDataStructure *pStruct = ((TestThreadDataStructure *)pArg);
    long int *pStoreHere = pStruct->pStoreTIDLoc;
    std::atomic<bool> *pTIDStoredLoc = pStruct->pTIDStoredLoc;

    // Clean up
    delete pStruct;
    pStruct = NULL;
    pArg = NULL;

    // Get this thread ID
    const long int lThisTID = syscall(SYS_gettid);

    // Store this thread ID
    (*pStoreHere) = lThisTID;

    // Set that we have finished storing the thread ID
    pTIDStoredLoc->store(true);

    // While we are waiting for everything to be ready so that we can begin the test
    while (true)
    {
        // If we are now ready to begin the test
        if (g_bTestReadyToBegin.load())
        {
            // Stop waiting
            break;
        }
    }

    // The loop iteration count
    uint64_t uCount = 0;

    // For the life of the test, i.e. forever
    while (true)
    {
        // Increment the count
        uCount++;

        // If we are about to go over the edge
        if (uCount >= (UINT64_MAX - 1))
        {
            // Reset the count
            uCount = 0;
        }

        // Every so often
        if ((uCount % 500000) == 0)
        {
            // Print our progress
            printf("Thread %05ld: uCount = %lu\n", lThisTID, uCount);
        }

        // Get the mutex owner's thread ID
        iOwner = g_TestMutex.native_handle()->__data.__owner;

        // Ensure that this is a valid thread ID
        AssertIsValidTID(iOwner);

        // Lock the mutex as part of the test
        g_TestMutex.lock();

        // Get the mutex owner's thread ID
        iOwner = g_TestMutex.native_handle()->__data.__owner;

        // Ensure that this is a valid thread ID
        AssertIsValidTID(iOwner);

        // Unlock the mutex as part of the test
        g_TestMutex.unlock();

        // Get the mutex owner's thread ID
        iOwner = g_TestMutex.native_handle()->__data.__owner;

        // Ensure that this is a valid thread ID
        AssertIsValidTID(iOwner);
    }
}

// Start the test
void StartTest()
{
    // For each thread to launch
    for (int i = 0; i < NUM_THREADS_TO_LAUNCH; i++)
    {
        // Initialize that we do not have a thread ID yet
        g_AllSpecialThreadsTIDs[i] = 0;
        g_bEachTestThreadIsReadyToBegin[i].store(false);
    }

    // For each thread to launch
    for (int i = 0; i < NUM_THREADS_TO_LAUNCH; i++)
    {
        // Allocate a data structure with which to pass data to each thread
        TestThreadDataStructure *pDataStruct = new TestThreadDataStructure;

        // Store the location at which the thread should place its thread ID
        pDataStruct->pStoreTIDLoc = ((long int *)((&(g_AllSpecialThreadsTIDs[i]))));

        // Store the location of the atomic variable that each thread should set to true when it has finished storing its thread ID
        pDataStruct->pTIDStoredLoc = ((std::atomic<bool> *)((&(g_bEachTestThreadIsReadyToBegin[i]))));

        // The thread to return
        boost::thread *pNewThread = NULL;

        // Launch the new thread
        try { pNewThread = new boost::thread(TestMutexTesterThread, pDataStruct); }

        // Catch errors
        catch (boost::thread_resource_error &ResourceError)
        {
            // Print this error
            printf("boost::thread construction error: '%s'", ResourceError.what());

            // This is a fatal error
            abort();
        }

        // Clean up
        delete pNewThread;
        pNewThread = NULL;
    }

    // Whether or not all threads are ready to begin
    bool bAllThreadsReadyToBegin = false;

    // While we are waiting for all threads to be ready to begin
    while (true)
    {
        // Reset to assuming all threads are ready to begin
        bAllThreadsReadyToBegin = true;

        // For each thread we launched
        for (int i = 0; i < NUM_THREADS_TO_LAUNCH; i++)
        {
            // If this thread has not yet stored its thread ID
            if (g_bEachTestThreadIsReadyToBegin[i].load() == false)
            {
                // We are not yet ready to begin
                bAllThreadsReadyToBegin = false;

                // Start over
                break;
            }
        }

        // If all threads are ready to begin
        if (bAllThreadsReadyToBegin)
        {
            // We are done waiting
            break;
        }
    }

    // Atomically store that all threads are ready to begin and that the test should proceed
    g_bTestReadyToBegin.store(true);
}

1 个答案:

答案 0 :(得分:-1)

它是'安全的',因为读取和写入最多8个字节,与相应的字节数对齐,是原子的。假设x86_64

我不知道提升,但你的宏看起来很糟糕:

  • 你进行内部数据布局,这使得容易出现api和abi破坏
  • 检查“我有锁吗”几乎总是一个设计错误(程序逻辑应该决定你是否这样做,因此无需检查)。除了这种类型的访问减慢了争用下的代码 - 另一个cpu可能会使用锁定字弄脏缓存行,现在你无法正常获取
  • 检查“有人有锁吗”本质上是活泼的,通常是错误的。如果您想获取锁定(如果可用但是否则失败),您可以尝试锁定