互斥锁和内存一致性之间的区别?
Difference between mutexes and memory coherence?
我知道多核架构的内存一致性协议。例如,MSI 允许最多一个内核在启用读写访问的情况下将高速缓存行保存在 M 状态。 S状态允许同一行的多个共享者只读取数据。 I state 不允许访问当前获取的缓存行。 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);
这是使它正常工作的唯一方法。现在为什么我们必须提供这些互斥体?为什么这不直接由内存一致性处理?为什么我们需要互斥锁或原子指令?
您的代码行sum = sum + temp_sum;
虽然在C语言中看起来很简单,但它不是原子操作。它将 sum
的值从内存加载到寄存器中,对其执行算术运算(将 temp_sum
的值相加),然后将结果写回内存(存储 sum
的任何位置)。
即使一次只有一个线程可以从内存中读取或写入 sum
,仍然有可能出现同步问题。第二个线程可以修改内存中的 sum
而第一个线程正在操作寄存器中的值。然后第一个线程会将它认为是更新值(算术结果)写回内存,覆盖第二个放在那里的任何内容。正是寄存器中的这个过渡位置引入了这个问题。 "the value of a variable" 的概念比当前驻留在内存中的任何内容都要多。
例如,假设sum
最初是4,两个线程想给它加1。第一个线程将 4 从内存加载到寄存器中,然后加 1 得到 5。但是在第一个线程可以将结果存储回内存之前,第二个线程加载 4,加 1,然后将 5 写回内存。然后第一个线程继续并将其结果 (5) 存储回相同的内存位置。两个线程都确信他们已经完成了他们的职责并正确地更新了 sum
。问题是 sum
是 5 而不是应该的 6。
互斥量确保一次只有一个线程加载、修改和存储sum
。任何第二个线程都必须等待(被阻塞)直到第一个线程完成。
我知道多核架构的内存一致性协议。例如,MSI 允许最多一个内核在启用读写访问的情况下将高速缓存行保存在 M 状态。 S状态允许同一行的多个共享者只读取数据。 I state 不允许访问当前获取的缓存行。 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);
这是使它正常工作的唯一方法。现在为什么我们必须提供这些互斥体?为什么这不直接由内存一致性处理?为什么我们需要互斥锁或原子指令?
您的代码行sum = sum + temp_sum;
虽然在C语言中看起来很简单,但它不是原子操作。它将 sum
的值从内存加载到寄存器中,对其执行算术运算(将 temp_sum
的值相加),然后将结果写回内存(存储 sum
的任何位置)。
即使一次只有一个线程可以从内存中读取或写入 sum
,仍然有可能出现同步问题。第二个线程可以修改内存中的 sum
而第一个线程正在操作寄存器中的值。然后第一个线程会将它认为是更新值(算术结果)写回内存,覆盖第二个放在那里的任何内容。正是寄存器中的这个过渡位置引入了这个问题。 "the value of a variable" 的概念比当前驻留在内存中的任何内容都要多。
例如,假设sum
最初是4,两个线程想给它加1。第一个线程将 4 从内存加载到寄存器中,然后加 1 得到 5。但是在第一个线程可以将结果存储回内存之前,第二个线程加载 4,加 1,然后将 5 写回内存。然后第一个线程继续并将其结果 (5) 存储回相同的内存位置。两个线程都确信他们已经完成了他们的职责并正确地更新了 sum
。问题是 sum
是 5 而不是应该的 6。
互斥量确保一次只有一个线程加载、修改和存储sum
。任何第二个线程都必须等待(被阻塞)直到第一个线程完成。