2014-09-11 4 views
2

안녕하세요, 정수가 아닌 값으로 쓰는 중 찢어지는 int 및 float 예제가 필요합니다. 나는 이것을 재현 할 수 없다. 그것은 매우 드물거나 내가 잘못하고있는 어떤 것으로 보인다.non-atomics가 찢어 지도록하기

인쇄되지 않는 테스트 코드는 다음과 같습니다. 거기에 문제가 있습니까? 내가 찢어 트리거 할 수 있습니다

#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); 
} 
+0

32 비트 액세스의 경우 Intel 플랫폼에서 재생성 할 수 없기 때문에 이것을 재현 할 수 없습니다. 원자 변수는 테스트하지 않은 다른 문제, 즉 원자 값에 대한 액세스 이전과 이후의 변경 사항의 가시성을 해결합니다. 예를 들어 메모리 울타리가없는 비 원자 32 비트 액세스를 사용하여 스핀 록을 구현 한 경우 문제가 발생합니다. –

+0

인텔 플랫폼에서 찢어진 32 비트 읽기 또는 쓰기를 관찰하려면 최소한 캐시 라인 경계 또는 심지어 페이지 경계를 가로 지르는 정렬되지 않은 액세스가 필요합니다. 일반적으로 찢어진 32 비트 읽기 또는 쓰기는 인텔 플랫폼에서 일반적인 정렬로 발생하지 않을 것입니다. 어떤 상황에서 트리거 될 수 있는지 확실하지 않습니다. – mattnewport

답변

2

이/비주얼 스튜디오 2013 컴파일 할 때 캐시 라인 경계를 가로 지르는하는 주장 uint32_t를 강제로이 테스트 코드를 읽기와 쓰기 (전용 릴리스에서 일어날 것으로 보인다 빌드) :

#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 
+0

이것은 아주 좋습니다. 나는 충돌없이 그것을 실행할 수 없었다. VS2012가없는 것으로 추정합니다. writeVal은 i에 대한 커다란 가치를 얻고 있습니다. 인라인 코드를 고려해 보겠습니다. – user1335325

+0

좋아, 조금 더 많은 정보 ... 나는 void writeVal (volatile uint32_t * pVal, volatile int tid); 하지만 이것은 여전히 ​​VS2012에서 잘못된 액세스라고보고하는 나를 위해 충돌합니다. * pVal = vals [tid]; – user1335325

+0

마지막 문제는 2012 년에 변경된 사항입니다. InterlockedExchange를 변경하여 문제가 해결되었음을 확인했습니다. 대단히 고마워요 ... 이것은 많은 도움이 될 것입니다. – user1335325

0

FWIW, 이것은 이전 답변을 단지 추가 정보 나 그냥 페이지에 대한 의견을 이동할 수 있습니다보다 높은 유래 권한을 가진 사람입니다 거꾸로하는 질문.

실제로 찢어지는 주소를 확인했는데 예상대로 주소는 캐시 라인에 62 바이트이므로 32 비트 값은 한 캐시 라인의 마지막 두 바이트와 처음 두 바이트에 기록됩니다 다른 바이트. 아래의 gdb 출력을보십시오.

[email protected]:~/src/stackoverflow$ g++ -g -std=c++0x tear.cpp -pthread -o tear 

[email protected]:~/src/stackoverflow$ ./tear 

Detected torn read/write! pVal = 0x00c0503e currentVal = 0x33331111 
Detected torn read/write! pVal = 0x00c0503e currentVal = 0x44441111 
[email protected]:~/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