我了解多核架构的内存一致性协议。例如,MSI允许最多一个核心在M状态下保持高速缓存行,同时启用读取和写入访问。 S状态允许同一行的多个共享者仅读取数据。我状态允许无法访问当前获取的缓存行。 MESI通过添加一个只允许一个共享者读取的E状态来扩展它,如果没有其他共享者,则允许更容易地转换到M状态。
从我上面写的,我理解当我们将这行代码编写为多线程(pthreads)程序的一部分时:
// temp_sum is a thread local variable
// sum is a global shared variable
sum = sum + temp_sum;
它应允许一个线程在M状态下访问sum
使所有其他共享者无效,然后当另一个线程到达同一行时,它将请求M再次使当前共享者失效,依此类推。但事实上,除非我添加互斥锁,否则不会发生这种情况:
pthread_mutex_lock(&locksum);
// temp_sum is a thread local variable
// sum is a global shared variable
sum = sum + temp_sum;
pthread_mutex_unlock(&locksum);
这是使其正常工作的唯一方法。现在为什么我们必须提供这些互斥体?为什么直接由记忆连贯处理?为什么我们需要互斥或原子指令?
答案 0 :(得分:1)
您的代码行sum = sum + temp_sum;
虽然在C中看似简单,但它不是原子操作。它将sum
的值从内存加载到寄存器中,对其执行算术运算(添加temp_sum
的值),然后将结果写回内存(存储sum
的地方)。
即使一次只有一个线程可以从内存中读取或写入sum
,仍然存在同步问题的机会。第二个线程可以修改内存中的sum
,而第一个线程可以操作寄存器中的值。然后第一个线程会将它认为更新的值(算术结果)写回内存,覆盖第二个放在那里的内容。寄存器中的这个过渡位置引入了该问题。 “变量的价值”概念比目前存在于记忆中的任何东西都要多。
例如,假设sum
最初是4.两个线程想要为它添加1。第一个线程将4从内存加载到寄存器中,并将1加到make 5.但是在第一个线程将结果存储回内存之前,第二个线程加载4,加1,并将5写回内存。然后第一个线程继续并将其结果(5)存储回相同的存储器位置。两个线程都确信他们已经完成了自己的职责并正确更新了sum
。问题是sum
是5,而不是6。
互斥锁确保一次只能加载,修改和存储sum
一个线程。任何第二个线程都必须等待(被阻止)直到第一个线程完成。