导致非原子撕裂

时间:2014-09-11 18:39:45

标签: c++ windows multithreading atomic volatile

您好我想要一个int和一个浮动示例,它会导致使用非原子值进行撕裂。我似乎无法重现这一点。这似乎是非常罕见的事情,或者我做错了。

这是我从未打印过的测试代码。它有什么问题吗?

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>

#define MAX_THREADS 64
#define BUF_SIZE 255

DWORD WINAPI MyThreadFunction( LPVOID lpParam );
void ErrorHandler(LPTSTR lpszFunction);

// Sample custom data structure for threads to use.
// This is passed by void pointer so it can be any data type
// that can be passed using a single void pointer (LPVOID).
typedef struct MyData {
    int val1;
    int val2;
} MYDATA, *PMYDATA;


int _tmain()
{
    DWORD   dwThreadIdArray[MAX_THREADS];
    HANDLE  hThreadArray[MAX_THREADS]; 

    // Create MAX_THREADS worker threads.

    for( int i=0; i<MAX_THREADS; i++ )
    {
        // Allocate memory for thread data.
        // Create the thread to begin execution on its own.

        hThreadArray[i] = CreateThread( 
            NULL,                   // default security attributes
            0,                      // use default stack size  
            MyThreadFunction,       // thread function name
            NULL,                   // argument to thread function 
            0,                      // use default creation flags 
            &dwThreadIdArray[i]);   // returns the thread identifier 


        // Check the return value for success.
        // If CreateThread fails, terminate execution. 
        // This will automatically clean up threads and memory. 

        if (hThreadArray[i] == NULL) 
        {
            ErrorHandler(TEXT("CreateThread"));
            ExitProcess(3);
        }
    } // End of main thread creation loop.

    // Wait until all threads have terminated.

    WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);

    // Close all thread handles and free memory allocations.

    for(int i=0; i<MAX_THREADS; i++)
    {
        CloseHandle(hThreadArray[i]);
    }

    return 0;
}

#pragma pack(push, 1)
struct Test
{
    char x1;
    char x1;
    char x3;
    int test;
    char x4;
    char x5;
};

Test* t = new Test(); //This is test code don't care about allocation or that it is a global.
#pragma pack(pop)

DWORD WINAPI MyThreadFunction( LPVOID lpParam ) 
{ 
    HANDLE hStdout;

    TCHAR msgBuf[BUF_SIZE];
    size_t cchStringSize;
    DWORD dwChars;

    // Make sure there is a console to receive output results. 

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if( hStdout == INVALID_HANDLE_VALUE )
        return 1;

    static int thread = 0;
    StringCchPrintf(msgBuf, BUF_SIZE, TEXT("Starting thread, %d\n"), ++thread); 
    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
    WriteConsole(hStdout, msgBuf, (DWORD)cchStringSize, &dwChars, NULL);

    t->test = 1;

    for (int i=0; i<1000000000;++i)
    {
        t->test = 1;
        t->test = 10000;
        t->test = 10000000;

        int result = t->test;

        if(result != 1 && result != 10000 && result != 10000000)
        {
            StringCchPrintf(msgBuf, BUF_SIZE, TEXT("Tearing occured = %d\n"), result); 
            StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
            WriteConsole(hStdout, msgBuf, (DWORD)cchStringSize, &dwChars, NULL);
        }
    }


    return 0; 
} 



void ErrorHandler(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code.

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message.

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK); 

    // Free error-handling buffer allocations.

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

2 个答案:

答案 0 :(得分:2)

我可以使用此测试代码触发撕裂的读/写操作,这会强制争用的uint32_t在使用Visual Studio 2013编译时跨越缓存行边界(似乎只在发布版本中发生):

#include <algorithm>
#include <atomic>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

using namespace std;

atomic<bool> gDone = false;

vector<uint32_t> vals = {0x11111111, 0x22222222, 0x33333333, 0x44444444, };

mutex ioMutex;

void writeVal(volatile uint32_t* pVal, int tid) {
    while (!gDone) {
        *pVal = vals[tid];
        const auto currentVal = *pVal;
        auto findIt = find(begin(vals), end(vals), currentVal);
        if (findIt == end(vals)) {
            unique_lock<mutex> ul(ioMutex);
            cout << "Detected torn read/write! pVal = 0x" << setbase(16) << setw(8) << setfill('0')
                 << reinterpret_cast<uintptr_t>(pVal) << " currentVal = 0x" << currentVal << endl;
            gDone = true;
        }
    }
}

int main() {
    vector<char> memVec(16 * 1024);
    char* first = &memVec[0];
    const auto cacheLineSize = 64;
    char* crossesCacheLine =
        reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(first + cacheLineSize) & ~(cacheLineSize - 1)) - 2);
    uint32_t* tearableUint32 = reinterpret_cast<uint32_t*>(crossesCacheLine);
    vector<thread> threads(vals.size());
    for (int i = 0; i != threads.size(); ++i) {
        threads[i] = thread([=] { writeVal(tearableUint32, i); });
    }
    for (auto& t : threads) {
        t.join();
    }
}

输出:

Detected torn read/write! pVal = 0x004bc43e currentVal = 0x11112222

答案 1 :(得分:0)

FWIW,这只是前一个答案的附加信息,具有比我更高的stackoverflow权限的人可能只是将其移动到上一个问题的评论。

我刚检查了实际给出撕裂的地址,并且正如预期的那样,地址是高速缓存行的62个字节,因此32位值被写入一个高速缓存行的最后两个字节和前两个字节另一个请参阅下面的gdb输出。

alapaa@hilbert:~/src/stackoverflow$ g++ -g -std=c++0x tear.cpp -pthread -o tear  

alapaa@hilbert:~/src/stackoverflow$ ./tear  

Detected torn read/write! pVal = 0x00c0503e currentVal = 0x33331111  
Detected torn read/write! pVal = 0x00c0503e currentVal = 0x44441111  
alapaa@hilbert:~/src/stackoverflow$ gdb  
GNU gdb (Ubuntu 7.7-0ubuntu3.1) 7.7  
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".

(gdb) p 0x00c0503e % 64  
$1 = 62