Concurrency 如果两个线程读取&;写下同一段记忆

Concurrency 如果两个线程读取&;写下同一段记忆,concurrency,memory-access,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

此时读取线程具有过时数据。

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

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

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

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

如果两个线程将32位写入这样一个对齐的单元,那么发生的情况就完全可以定义清楚:两个写入值中的一个被保留。不幸的是,对于你(嗯,程序),你不知道哪个值。通过非常聪明的编程,您实际上可以使用这种读写原子性来构建同步算法(例如),但通常使用体系结构定义的锁会更快

如果两个线程写入的值超过一个原子单位(例如,它们都写入128位的值),那么事实上,写入的值中原子单位大小的部分将以一种完全定义好的方式存储,但您不知道哪个值的哪些部分以什么顺序写入。因此,存储中最终可能出现的是来自第一个线程、第二个线程的值,或者来自两个线程的原子单位大小的位的混合

类似的想法适用于单线程读取、单线程原子单位写入以及更大的线程

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

正如我在的回答中所暗示的,CPU缓存在多核系统中也起着作用。考虑下面的测试代码:

危险会来的,罗宾逊! 下面的代码将优先级提升为realtime,以获得更一致的结果—虽然这样做需要管理员权限,但如果在双核或单核系统上运行代码,请小心,因为您的计算机将在测试运行期间锁定

#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
}
#包括
#包括
const int RUNFOR=5000;
volatile bool终止=false;
易失性int值;
静态DWORD WINAPI计数器(LPVOID parm)
{
整数误差=0;
而(!终止)
{
值=(int)parm;
如果(值!=(整数)parm)
错误++;
}
printf(“\t线程%08X:%d个错误\n”,参数,错误);
返回0;
}
静态void运行测试(int affinity1,int affinity2)
{
终止=假;
德沃德假人;
句柄t1=CreateThread(0,0,计数器错误,(void*)0x1000,CREATE_挂起,&dummy);
句柄t2=CreateThread(0,0,计数器错误,(void*)0x2000,CREATE_挂起,&dummy);
SetThreadAffinityMask(t1,AffinityY1);
SetThreadAffinityMask(t2,affinity2);
恢复线程(t1);
恢复线程(t2);
printf(“使用相关性%d和%d运行测试%d毫秒\n”,RUNFOR,affinity1,affinity2);
睡眠(跑步);
终止=真;
Sleep(100);//让线程有机会选择“terminating”标志。
}
int main()
{
SetPriorityClass(GetCurrentProcess(),实时优先级类);
运行测试(1,2);//核心1和2
运行测试(1,4);//核心1和3
运行测试(4,8);//核心3和4
运行测试(1,8);//核心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 使用亲缘关系1和2运行测试5000毫秒 线程000020000:351883错误 线程000011000:343523错误 使用亲缘关系1和4运行测试5000毫秒 线程000011000:48073错误 线程000020000:59813错误 使用关联4和8运行5000毫秒的测试 线程000020000:337199错误 线程000011000:335467错误 使用亲缘关系1和8运行测试5000毫秒 线程000011000:55736错误 线程000020000:72441错误
这很有道理。非常感谢你。不是吹毛求疵,而是为了理解,让我们假设我知道我在一个32位处理器上,我的变量是32位(或更少)。如果只有一个线程正在写入,而其他线程正在读取,是否仍然存在数据损坏的可能性?换句话说,您提供的示例是唯一的问题吗