一般地,对于int num
,num++
(或++num
),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++
一条指令,我们可以得出结论,在这种情况下num++
是原子的吗?
如果是这样,是否意味着如此生成num++
可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>
并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock
前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
我听说有英特尔在线书籍描述了特定汇编指令所需的CPU周期,但我无法找到它(经过努力).有人能告诉我如何找到CPU周期吗?
下面是一个例子,在下面的代码中,mov/lock是1个CPU周期,xchg是3个CPU周期.
// This part is Platform dependent!
#ifdef WIN32
inline int CPP_SpinLock::TestAndSet(int* pTargetAddress,
int nValue)
{
__asm
{
mov edx, dword ptr [pTargetAddress]
mov eax, nValue
lock xchg eax, dword ptr [edx]
}
// mov = 1 CPU cycle
// lock = 1 CPU cycle
// xchg = 3 CPU cycles
}
#endif // WIN32
Run Code Online (Sandbox Code Playgroud)
顺便说一句:这是我发布的代码的URL:http://www.codeproject.com/KB/threads/spinlocks.aspx
Microsoft提供了InterlockedCompareExchange
执行原子比较和交换操作的功能.还有一个内在的._InterlockedCompareExchange
在x86上,这些是使用lock cmpxchg
指令实现的.
但是,通过阅读这三种方法的文档,他们似乎并不同意对齐要求.
英特尔的参考手册没有说明对齐(除了如果启用了对齐检查并且进行了未对齐的内存引用,则会生成异常)
我也查找了lock
前缀,具体说明了这一点
锁定前缀的完整性不会受到内存领域的对齐方式.
(强调我的)
所以英特尔似乎认为对齐是无关紧要的.无论如何,这个操作都是原子的.
该_InterlockedCompareExchange
固有的文档也只字未提对齐,但是InterlockedCompareExchange
功能指出,
该函数的参数必须在32位边界上对齐; 否则,该函数将在多处理器x86系统和任何非x86系统上表现不可预测.
什么给出了什么?对齐要求是否InterlockedCompareExchange
只是为了确保该功能即使在cmpxchg
指令不可用的486之前的CPU上也能正常工作?这看起来很可能基于上述信息,但在依赖它之前我想确定一下.:)
或者ISA需要对齐以保证原子性,我只是在英特尔的参考手册中找错了地方?
通常,C++中引用计数智能ptr类的最广为人知的实现,包括标准std::shared_ptr
,使用原子引用计数,但不提供对同一智能ptr实例的原子访问.换句话说,多个线程可以安全地在shared_ptr
指向同一共享对象的单独实例上操作,但是多个线程不能安全地读取/写入同一shared_ptr
实例的实例而不提供某种同步,例如互斥或其他.
已经提出了shared_ptr
被称为" atomic_shared_ptr
" 的原子版本,并且已经存在初步实现.据推测,可以使用自旋锁或互斥锁轻松实现,但也可以实现无锁实现.atomic_shared_ptr
在研究了其中一些实现后,有一件事是显而易见的:实现无锁std::shared_ptr
是非常困难的,并且似乎需要这么多compare_and_exchange
操作才能让我质疑简单的自旋锁是否会实现更好的性能.
实现无锁引用计数指针如此困难的主要原因是因为在读取共享控制块(或共享对象本身,如果我们讨论的是侵入式共享指针)之间总是存在竞争,并修改引用计数.
换句话说,您甚至无法安全地读取引用计数,因为您永远不知道其他某个线程何时释放了引用计数所在的内存.
因此,通常,采用各种复杂策略来创建无锁版本.这里的实现看起来像是使用双引用计数策略,其中有"本地"引用计算并发访问shared_ptr
实例的线程数,然后是"共享"或"全局"引用,它们计算指向shared_ptr实例的数量到共享对象.
考虑到所有这些复杂性,我真的很惊讶地找到了Dobbs博士的文章,从2004年开始,(在C++ 11原子之前的方式)似乎无情地解决了整个问题:
http://www.drdobbs.com/atomic-reference-counting-pointers/184401888
看起来作者声称能够以某种方式:
"... [读取]指向计数器的指针,递增计数器,并以这样的方式返回指针 - 所有其他线程都不会导致错误的结果"
但我真的不明白他实际实现这一点的方式.他正在使用(非便携式)PowerPC指令(LL/SC原语lwarx
和stwcx
)将其关闭.
执行此操作的相关代码是他所谓的aIandF
"(原子增量和提取)",他将其定义为:
addr aIandF(addr r1){
addr tmp;int c;
do{
do{
tmp = *r1;
if(!tmp)break;
c = lwarx(tmp);
}while(tmp != *r1);
}while(tmp && !stwcx(tmp,c+1));
return tmp;
};
Run Code Online (Sandbox Code Playgroud)
显然,addr …
什么(如果有的话)是x86 asm xchg
指令的C#等价物?
有了这个命令,哪个imo是一个真正的交换(不像Interlocked.Exchange
),我可以简单地自动交换两个int,这就是我真正想做的事情.
更新:
示例代码基于我的建议.变量后缀"_V"被装饰为volatile:
// PART 3 - process links
// prepare the new Producer
address.ProducerNew.WorkMask_V = 0;
// copy the current LinkMask
address.ProducerNew.LinkMask_V = address.Producer.LinkMask_V;
// has another (any) thread indicated it dropped its message link from this thread?
if (this.routerEmptyMask[address.ID] != 0)
{
// allow all other bits to remain on (i.e. turn off now defunct links)
address.ProducerNew.LinkMask_V &= ~this.routerEmptyMask[address.ID];
// reset
this.routerEmptyMask[address.ID] = 0;
}
// PART 4 - swap
address.ProducerNew = …
Run Code Online (Sandbox Code Playgroud) 假设一个5级流水线架构(IF =指令获取,ID =指令解码,EX =执行,MEM =存储器访问,WB =寄存器写回).有4条指令必须执行.
(这些样本说明不准确,但我相信这一点会被理解)
在第五个时钟周期,这些指令将在管道中,如下所示.
添加a,b,c [IF ID EX MEM WB]
添加a,b,d [IF ID EX MEM]
添加a,b,e [IF ID EX]
添加a,b,f [IF ID]
现在,如果发生硬件中断,这些指令会发生什么.只有在执行流水线中的所有指令后才能处理中断吗?是否会以不同的方式处理软件中断和异常?
我正在阅读allan cruse 代码的smphello.s 代码
在接下来的部分中,他试图为每个处理器设置堆栈段.
关键是他在xadd的描述中使用了xadd而没有使用锁定前缀,就像在这里一样.可能有一个锁定前缀.
这是一个错误还是没关系?为什么?
# setup an exclusive stack-area for this processor
mov $0x1000, %ax # paragraphs in segment
xadd %ax, newSS # 'atomic' xchg-and-add
mov %ax, %ss # segment-address in SS
xor %esp, %esp # top-of-stack into ESP
Run Code Online (Sandbox Code Playgroud) 你将如何在x86中实现128位原子操作?
英特尔系统编程指南,第1部分,8.1锁定原子操作指定保证16位,32位和64位原子操作.那么,你能用2个带有LOCK前缀的64位操作来实现128位原子操作吗?就像是...
LOCK mov 64bits->addr
LOCK mov 64bits->addr+64bits
Run Code Online (Sandbox Code Playgroud)
显然,SSE具有128位XMM寄存器.你能用这些寄存器进行128位比较和交换吗?
atomic_flag_test_and_set
是的!atomic_flag_clear
是的!atomic_flag_test_and_clear
没有atomic_flag_set
没有如果您想在某些上下文中对事件设置标志,并在其他上下文中检查并清除事件,C/C++ 不允许您在每个上下文中执行单个原子调用。
您必须反转标志,因此清除事件上的标志,在检查事件时检查并设置标志。
没什么大不了的,但在这种情况下似乎是倒退的,特别是考虑到标志的默认状态为 false,这在相反的意义上意味着默认情况下会断言事件。
我想,也可以使用原子bool
with来代替。atomic_exchange