线程写入缓冲区内存和线程内存



假设您有一个多线程运行的程序。在我的例子中,程序使用std::thread,但我认为这并不重要。现在,每个线程都需要写入某个全局缓冲区,但它们保证永远不会写入该缓冲区中的相同内存地址。问题:

  • 线程安全吗?
  • 是否有效?我的意思是,如果你有8个线程,它们可以访问缓冲区的不同部分。这可能会导致很多缓存丢失?

上面的代码只是我所说的"线程写入相同的缓冲区,但从不在相同的地址"的一个例子(尽管在我的情况下,缓冲区要大得多,线程访问缓冲区的不同部分)。

int *buffer = new buffer[10];
for (int i = 0; i < 10; ++i) {
    threads[k] = std::thread(threadFunc, std::ref(buffer), i);
}
void threadFunc(int *&buffer, const int &threadId)
{
    buffer[threadId] = threadId;
}

如果两个线程在没有同步的情况下执行冲突的访问,则会产生未定义的行为(竞争条件,甚至更糟)。

相同内存位置的两次访问发生冲突。单独访问数组元素是安全的。

在标准中,1.7p3规定

内存位置要么是标量类型的对象,要么是宽度都不为零的相邻位域的最大序列。[注:语言的各种特性,如引用和虚函数,可能涉及到程序无法访问但由实现管理的额外内存位置。]两个或多个线程的执行(1.10)可以更新和访问不同的内存位置,而不会相互干扰。

然而,它可能不会是有效的。如果多个数组元素放到一个缓存行中,那么线程就会争夺所有权。这被称为"虚假共享",基本上否定了首先使用缓存的性能优势。为了克服这个问题,可能需要添加填充,以便不同的数组元素存在于不同的缓存行中。

…但是它们保证永远不会写入相同的内存地址这个缓冲区

这使它线程安全。读是有效的,但是写可能会在某些架构上造成缓存争用。

但是,缓冲区必须相当大,因为线程id通常有数百个。你不能使用像地图这样没有同步的策略,所以由于稀疏性,你必须使用像buffer[12]buffer[134]这样的稀疏数组。除非你给每个线程分配一个自己的ID,这需要每个线程的状态…这个想法被我下面的观点所取代。 最好的策略通常是让每个线程的堆栈保存一个指向静态内存位置的指针,并将此上下文作为参数传递给线程中需要它的各种函数。这就是上下文对象在多线程编程中如此有用的原因。

最新更新