我很好奇,是否知道使用默认的new运算符分配内存是否是非阻塞操作。
例如

struct Node {
    int a,b;
};
...
Node foo = new Node();
如果多个线程试图创建一个新的节点,并且其中一个在分配过程中被操作系统挂起,是否会阻止其他线程取得进展?
我问的原因是因为我有一个创建新节点的并发数据结构。然后,我修改了算法以回收节点。在24核计算机上,这两种算法的吞吐性能几乎相同。但是,我然后创建了一个在所有系统核心上运行的干扰程序,以创建尽可能多的OS抢占。相对于回收节点的算法,创建新节点的算法的吞吐量性能降低了5倍。
我很好奇为什么会发生这种情况。
谢谢。
*编辑:将我指向Linux的C++内存分配器的代码也将有所帮助。在发布此问题之前,我尝试过查找,但是找不到它。

最佳答案

在我看来,如果您的干扰应用程序使用的是new/delete(malloc/free),那么该干扰应用程序将对非循环测试产生更大的干扰。但是我不知道您的干扰测试是如何实现的。

取决于您的回收方式(即,如果您使用pthread互斥锁,请禁止使用),您的回收代码可能会很慢(gcc原子操作在实现回收时会快40倍)。

在至少某些平台上很长时间以来,Malloc一直在意识到线程。请使用gcc上的编译器开关来确保您了解它。较新的算法会为每个线程维护小内存块的池,因此,如果线程中有可用的小项,则不会有阻塞或阻塞很少。我已经简化了这一点,这取决于您的系统正在使用什么malloc。另外,如果您去分配数百万个项目进行测试....那么,您将看不到这种效果,因为小项目池的大小受到限制。也许你会的。我不知道。如果您在分配后立即释放了该项目,则您更有可能看到它。已释放的小项目将返回小项目列表,而不是共享堆。尽管“当线程B释放线程A分配的项目时会发生什么”是一个问题,该问题可能会或可能不会在您的malloc版本上处理,并且可能不会以非阻塞方式处理。可以肯定的是,如果您在大型测试期间没有立即释放,则该线程将不得不多次填充其小型项目列表。如果尝试了多个线程,则可能会阻塞。最后,在某个时候,您的进程的堆会向系统询问堆内存,这显然会阻塞。

那么,您使用的是小型存储项目吗?对于您的malloc,我不知道会有多小,但是如果您
如何通过原子操作进行回收(CAS =比较和交换):

首先将pNextFreeNode添加到您的节点对象。我使用了void *,可以使用您的类型。该代码用于32位指针,但也适用于64位。然后建立一个全局性的回收堆。

void *_pRecycleHead; // global head of recycle list.

添加到回收堆中:
void *Old;
while (1) { // concurrency loop
  Old = _pRecycleHead;  // copy the state of the world. We operate on the copy
  pFreedNode->pNextFreeNode = Old; // chain the new node to the current head of recycled items
  if (CAS(&_pRecycleHead, Old, pFreedNode))  // switch head of recycled items to new node
    break; // success
}

从桩上移走:
void *Old;
while (Old = _pRecycleHead) { // concurrency loop, only look for recycled items if the head aint null
  if (CAS(&_pRecycleHead, Old, Old->pNextFreeNode))  // switch head to head->next.
    break; // success
}
pNodeYoucanUseNow = Old;

使用CAS意味着只有当您更改的项目是您传递的旧值时,操作才会成功。如果有种族,并且另一个线程首先到达那里,那么旧​​值将有所不同。在现实生活中,这种比赛很少发生。 CAS仅比实际设置的速度慢一些,因此与互斥锁比较。

如果您快速添加和删除同一项目,则上述“从桩中删除”具有竞争条件。我们通过在CAS'able数据中添加版本号来解决该问题。如果您在与回收桩头的指针同时执行版本号,您将获胜。使用 union 。 CAS 64位不花额外费用。
union TRecycle {
  struct {
    int iVersion;
    void *pRecycleHead;
  } ;  // we can set these.  Note, i didn't name this struct.  You may have to if you want ANSI
  unsigned long long n64;  // we cas this
}

注意,对于64位操作系统,您将必须使用128位结构。所以全局回收堆现在看起来像这样:
TRecycle _RecycleHead;

添加到回收堆中:
while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  pFreedNode->pNextFreeNode = Old.pRecycleHead;  // link item to be recycled into recycle pile
  New.pRecycleHead = pFreedNode;  // make the new state
  New.iVersion++;  // adding item to list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // now if version changed...we fail
    break; // success
}

从桩上移走:
while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  New.pRecycleHead = New.pRecycledHead.pNextFreeNode;  // new will skip over first item in recycle list so we can have that item.
  New.iVersion++;  // taking an item off the list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // we fail if version is different.
    break; // success
}
pNodeYouCanUseNow = Old.pRecycledHead;

我敢打赌,如果您以这种方式回收利用,您会看到性能提高。

关于c++ - Linux中的内存分配是非阻塞的吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4600208/

10-13 08:44