如果两个线程读取&写同一块内存

时间:2010-08-26 23:20:18

标签: concurrency memory-access

我的理解是,如果两个线程正在从同一块内存中读取,并且没有线程正在写入该内存,那么操作是安全的。但是,我不确定如果一个线程正在读取而另一个正在写入会发生什么。会发生什么?结果是否未定义?或者阅读是否陈旧?如果陈旧的读取不是一个问题,是否可以对变量进行非同步读写?或者数据是否可能被破坏,读取和写入都不正确,在这种情况下应该始终同步?

我想说,我已经知道这是后一种情况,内存访问的竞赛使状态未定义......但我不记得我在哪里学到了这些并且我很难时间在谷歌找到答案。我的直觉是变量在寄存器中操作,并且真实(如在硬件中)并发是不可能的(或者是它),因此可能发生的最坏情况是陈旧数据,即以下内容:

WriteThread: copy value from memory to register
WriteThread: update value in register
ReadThread:  copy value of memory to register
WriteThread: write new value to memory

此时读取线程有陈旧数据。

3 个答案:

答案 0 :(得分:9)

通常,内存是以CPU体系结构确定的原子单位读取或写入的(32位和64位项目在32位和64位边界上对齐这些天很常见。)

在这种情况下,会发生什么取决于正在写入的数据量。

让我们考虑32位原子读/写单元的情况。

如果两个线程将32位写入这样一个对齐的单元格,那么绝对定义会发生什么:保留两个写入值中的一个。不幸的是你(好吧,程序),你不知道哪个值。通过极其聪明的编程,您实际上可以使用读取和写入的原子性来构建同步算法(例如,Dekker's algorithm),但通常使用体系结构定义的锁更快。

如果两个线程写的更多比原子单元(例如,它们都写入128位值),那么实际上写入的值的原子单元大小的部分将被存储在一个绝对的定义的方式,但将不知道哪个值的哪些部分以什么顺序写入。因此,最终存储的是来自第一个线程,第二个线程或两个线程的原子单元大小的位混合的值。

类似的想法适用于一个线程读取,一个线程以原子单位写入,更大。

基本上,您不希望对内存位置进行不同步的读写操作,因为您不会知道结果,即使它可能由架构很好地定义。

答案 1 :(得分:8)

结果未定义。数据损坏完全是可能的。举一个明显的例子,考虑一个由32位处理器操纵的64位值。假设该值是一个简单的计数器,当低32位包含0xffffffff时,我们递增它。增量产生0x00000000。当我们检测到它时,我们增加上面的单词。但是,如果某个其他线程在低位字递增和高位字递增的时间之间读取值,则它们会得到一个带有未递增的高位字的值,但是低位字设置为0 - 一个完全不同的值从增量完成后之前的内容开始。

答案 2 :(得分:0)

正如我在Ira Baxter的回答中暗示的那样,CPU缓存也在多核系统中起作用。请考虑以下测试代码:

危险罗宾逊!

以下代码提高了实时优先级,以实现更一致的结果 - 这样做需要管理员权限,在双核或单核系统上运行代码时要小心,因为您的计算机将在试运行。

#include <windows.h>
#include <stdio.h>

const int RUNFOR = 5000;
volatile bool terminating = false;
volatile int value;

static DWORD WINAPI CountErrors(LPVOID parm)
{
    int errors = 0;
    while(!terminating)
    {
        value = (int) parm;
        if(value != (int) parm)
            errors++;
    }
    printf("\tThread %08X: %d errors\n", parm, errors);
    return 0;
}

static void RunTest(int affinity1, int affinity2)
{
    terminating = false;
    DWORD dummy;
    HANDLE t1 = CreateThread(0, 0, CountErrors, (void*)0x1000, CREATE_SUSPENDED, &dummy);
    HANDLE t2 = CreateThread(0, 0, CountErrors, (void*)0x2000, CREATE_SUSPENDED, &dummy);

    SetThreadAffinityMask(t1, affinity1);
    SetThreadAffinityMask(t2, affinity2);
    ResumeThread(t1);
    ResumeThread(t2);

    printf("Running test for %d milliseconds with affinity %d and %d\n", RUNFOR, affinity1, affinity2);
    Sleep(RUNFOR);
    terminating = true;
    Sleep(100); // let threads have a chance of picking up the "terminating" flag.
}

int main()
{
    SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    RunTest(1, 2);      // core 1 & 2
    RunTest(1, 4);      // core 1 & 3
    RunTest(4, 8);      // core 3 & 4
    RunTest(1, 8);      // core 1 & 4
}

在我的四核intel Q6600系统上(iirc有两组内核,每组共享二级缓存 - 无论如何都会解释结果;)),我得到以下结果:

Running test for 5000 milliseconds with affinity 1 and 2
        Thread 00002000: 351883 errors
        Thread 00001000: 343523 errors
Running test for 5000 milliseconds with affinity 1 and 4
        Thread 00001000: 48073 errors
        Thread 00002000: 59813 errors
Running test for 5000 milliseconds with affinity 4 and 8
        Thread 00002000: 337199 errors
        Thread 00001000: 335467 errors
Running test for 5000 milliseconds with affinity 1 and 8
        Thread 00001000: 55736 errors
        Thread 00002000: 72441 errors