11

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

我想说我已经了解到这是后一种情况,即内存访问竞赛导致状态未定义......但我不记得我可能在哪里学到的,而且我很难找到在谷歌上回答。我的直觉是变量在寄存器中被操作,并且真正的(如在硬件中)并发是不可能的(或者是它),所以可能发生的最坏的情况是陈旧的数据,即以下:

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

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

4

3 回答 3

12

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

于 2010-08-26T23:24:17.707 回答
12

通常,内存以由 CPU 架构确定的原子单位读取或写入(如今,32 位和 64 位项目在 32 位和 64 位边界上对齐很常见)。

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

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

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

如果两个线程写入多于一个原子单元(例如,它们都写入一个 128 位值),那么实际上写入的值的原子单元大小的片段将以绝对明确的方式存储,但不会知道哪个哪个值的片段以什么顺序写入。因此,最终存储在存储中的是来自第一个线程、第二个线程的值,或者来自两个线程的原子单元大小的位的混合。

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

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

于 2010-08-26T23:29:44.670 回答
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
}

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

使用亲和力 1 和 2 运行 5000 毫秒的测试
        线程 00002000:351883 错误
        线程 00001000:343523 错误
以亲和力 1 和 4 运行 5000 毫秒的测试
        线程 00001000:48073 错误
        线程 00002000:59813 错误
以亲和力 4 和 8 运行 5000 毫秒的测试
        线程 00002000:337199 错误
        线程 00001000:335467 错误
以亲和力 1 和 8 运行 5000 毫秒的测试
        线程 00001000:55736 错误
        线程 00002000:72441 错误
于 2010-08-27T00:33:09.937 回答