我很好奇,是否知道使用默认的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,我不知道会有多小,但是如果您<1k,那肯定很小。您是一个接一个地分配和释放,还是先分配数千个节点,然后释放数千个节点?您的干扰应用正在分配吗?所有这些都会影响结果。
如何通过原子操作进行回收(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数据中添加版本号来解决该问题。如果在指向回收桩头的指针的同时执行版本号,您将获胜。使用工会。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;
我敢打赌,如果您以这种方式回收利用,您会看到性能提高。
问题内容: 我读过某个地方,Java可以在大约12条机器指令中为对象分配内存。这对我来说非常令人印象深刻。据我了解,JVM使用的技巧之一是按块预分配内存。我认为,这有助于最大程度地减少对操作系统的请求数量,这是非常昂贵的。但是,即使是CAS操作,在现代处理器上也可能要花费多达150个周期。 那么,谁能在Java中解释内存分配的实际成本以及JVM使用哪些技巧来加快分配速度? 问题答案: JVM为每个
本文向大家介绍verilog 非阻塞分配,包括了verilog 非阻塞分配的使用技巧和注意事项,需要的朋友参考一下 示例 非阻塞分配(<=)用于边缘敏感always块内部的分配。在一个块内,新值只有在处理完整个块后才可见。例如: 请注意<=此处使用了非阻塞()分配。由于第一个赋值直到在程序块之后才真正生效,因此第二个赋值会按预期执行并实际上交换两个变量-与阻塞赋值(=)或其他语言的赋值不同;f1仍
我有一个顶点,它有一个处理程序,可以在事件循环线程中调用Vertx的Web客户端。实际的底层API调用是同步的还是异步的?它会阻塞我的事件循环线程吗?假设我的API调用需要30秒才能返回。 我是否需要用Vertx.execute阻塞(p-
问题内容: 我有这段代码可以在Linux中从Serial读取,但是我不知道在读取SerialPort时阻塞和非阻塞之间有什么区别,在哪种情况下哪个更好? 问题答案: 您提到的代码是IMO编码和注释不当的代码。该代码不符合POSIX的可移植性惯例,如正确设置终端模式和POSIX操作系统的串行编程指南中所述。该代码没有提到它使用非规范(也称为原始)模式,并且重用了“阻塞”和“非阻塞”术语来描述 VMI
问题内容: 我正在尝试在Go中创建服务器和客户端,我已经设法与服务器和客户端进行通信。但是我的问题是,在golang中读取的TCP是非阻塞的。我想知道的是,golang中的读取是否有可能像C中的读取一样被阻塞。谢谢 编辑: 这是服务器的源代码: 和我的客户: 问题答案: 可以返回部分数据。从该文档,“如果某些数据是可用的,但不是LEN(P)个字节,读取常规返回什么是可用的,而不是等待更多。” 无论
9.7. 示例: 并发的非阻塞缓存 本节中我们会做一个无阻塞的缓存,这种工具可以帮助我们来解决现实世界中并发程序出现但没有现成的库可以解决的问题。这个问题叫作缓存(memoizing)函数(译注:Memoization的定义: memoization 一词是Donald Michie 根据拉丁语memorandum杜撰的一个词。相应的动词、过去分词、ing形式有memoiz、memoized、me