我想在我的C / C ++程序中使用“_test_and_set lock”汇编语言实现和原子交换汇编指令。
class LockImpl
{
public:
static void lockResource(DWORD resourceLock )
{
__asm
{
InUseLoop: mov eax, 0;0=In Use
xchg eax, resourceLock
cmp eax, 0
je InUseLoop
}
}
static void unLockResource(DWORD resourceLock )
{
__asm
{
mov resourceLock , 1
}
}
};
这有效,但这里有一个错误。
问题是我想传递DWORD * resourceLock而不是DWORD resourceLock。
所以问题是如何将指针从C / C ++传递到程序集并将其取回。 ?
提前谢谢。此致 -Jay。
P.S。这样做是为了避免用户空间和内核空间之间的上下文切换。
答案 0 :(得分:6)
如果您是为Windows编写的,则应认真考虑使用critical section对象。关键部分API函数经过优化,除非确实需要,否则它们不会转换到内核模式,因此无争用的正常情况下开销很小。
你的旋转锁定的最大问题是如果你在一个单独的CPU系统而你正在等待锁定,那么你就可以使用所有的循环而且持有锁定的任何东西都不会有机会一直运行,直到你的时间片结束,内核抢占你的线程。
使用关键部分比尝试滚动自己的用户模式自旋锁更成功。
答案 1 :(得分:4)
就您的实际问题而言,它非常简单:只需更改函数标题即可使用volatile DWORD *resourceLock
,并更改触及resourceLock
的装配线以使用间接:
mov ecx, dword ptr [resourceLock]
xchg eax, dword ptr [ecx]
和
mov ecx, dword ptr [resourceLock]
lock mov dword ptr [ecx], 1
但请注意,您还有其他一些问题迫在眉睫:
你说你在Windows上开发这个,但想切换到Linux。但是,您正在使用特定于MSVC的内联汇编 - 当您转移到Linux时,必须将其移植到gcc样式(特别是涉及从Intel语法切换到AT& T语法)。即使在Windows上,使用gcc进行开发也会更好 这样可以最大限度地减少迁移的痛苦(请参阅ming for Windows的gcc)。
Greg Hewgill对于无用地旋转是绝对正确的,阻止锁定器获得CPU。如果你已经旋转太久,请考虑让CPU产生。
在多处理器x86上,您可能会遇到内存加载问题以及在锁定周围重新排序的商店 - 可能需要锁定和解锁过程中的mfence
指令。
真的,如果你担心锁定意味着你正在使用线程,这可能意味着你已经在使用特定于平台的线程API。因此,请使用本机同步原语,并在切换到Linux时切换到pthreads版本。
答案 2 :(得分:3)
显然,您正在使用C ++代码中的内联汇编块来编译MSVC。
作为一般性评论,您应该使用compiler intrinsics,因为内联汇编没有前途:在编译x64时不再支持我的MS编译器。
如果您需要在装配中精确调整功能,则必须在单独的文件中实现它们。
答案 3 :(得分:1)
你应该使用这样的东西:
volatile LONG resourceLock = 1;
if(InterlockedCompareExchange(&resourceLock, 0, 1) == 1) {
// success!
// do something, and then
resourceLock = 1;
} else {
// failed, try again later
}
答案 4 :(得分:1)
问题中原始版本的主要问题是它需要使用寄存器间接寻址并获取引用(或指针参数)而不是锁DWORD的by-value参数。
这是Visual C ++的可行解决方案。 编辑: 我与作者脱机工作,我们已经验证了此答案中的代码在他的测试工具中正确运行。
但如果您使用的是Windows,则应该使用Interlocked API(即InterlockedExchange)。
编辑:正如CAF所述,lock xchg
不是必需的,因为xchg
会自动断言BusLock。
我还添加了一个更快的版本,在尝试执行xchg
之前执行非锁定读取。这显着减少了内存接口上的BusLock争用。通过对持有很长时间的锁进行退避(yield,然后睡眠),可以加快算法(在一个有争议的多线程情况下)。对于单线程CPU的情况,使用在锁定时立即休眠的OS锁定将是最快的。
class LockImpl
{
// This is a simple SpinLock
// 0 - in use / busy
// 1 - free / available
public:
static void lockResource(volatile DWORD &resourceLock )
{
__asm
{
mov ebx, resourceLock
InUseLoop:
mov eax, 0 ;0=In Use
xchg eax, [ebx]
cmp eax, 0
je InUseLoop
}
}
static void lockResource_FasterVersion(DWORD &resourceLock )
{
__asm
{
mov ebx, resourceLock
InUseLoop:
mov eax, [ebx] ;// Read without BusLock
cmp eax, 0
je InUseLoop ;// Retry Read if Busy
mov eax, 0
xchg eax, [ebx] ;// XCHG with BusLock
cmp eax, 0
je InUseLoop ;// Retry if Busy
}
}
static void unLockResource(volatile DWORD &resourceLock)
{
__asm
{
mov ebx, resourceLock
mov [ebx], 1
}
}
};
// A little testing code here
volatile DWORD aaa=1;
void test()
{
LockImpl::lockResource(aaa);
LockImpl::unLockResource(aaa);
}
答案 5 :(得分:0)
查看编译器文档,了解如何为函数打印生成的汇编语言。
打印此功能的汇编语言:
static void unLockResource(DWORD resourceLock )
{
resourceLock = 0;
return;
}
这可能不起作用,因为编译器可以优化函数并删除所有代码。您应该更改上面的函数以传递指向resourceLock
的指针,然后让函数设置锁定。打印此工作功能的组件。
答案 6 :(得分:-1)
我已经提供了一个工作版本,它解答了原始海报的问题,如何获取ASM中传递的参数以及如何使其锁定正常工作。
许多其他答案都质疑使用ASM的明智性,并提到应该使用内在函数或C OS调用。以下也适用,是我的ASM答案的C ++版本。如果您的平台不支持InterlockedExchange(),那么只需要使用ASM片段。
class LockImpl
{
// This is a simple SpinLock
// 0 - in use / busy
// 1 - free / available
public:
#if 1
static DWORD MyInterlockedExchange(volatile DWORD *variable,DWORD newval)
{
// InterlockedExchange() uses LONG / He wants to use DWORD
return((DWORD)InterlockedExchange(
(volatile LONG *)variable,(LONG)newval));
}
#else
// You can use this if you don't have InterlockedExchange()
// on your platform. Otherwise no ASM is required.
static DWORD MyInterlockedExchange(volatile DWORD *variable,DWORD newval)
{
DWORD old;
__asm
{
mov ebx, variable
mov eax, newval
xchg eax, [ebx] ;// XCHG with BusLock
mov old, eax
}
return(old);
}
#endif
static void lockResource(volatile DWORD &resourceLock )
{
DWORD oldval;
do
{
while(0==resourceLock)
{
// Could have a yield, spin count, exponential
// backoff, OS CS fallback, etc. here
}
oldval=MyInterlockedExchange(&resourceLock,0);
} while (0==oldval);
}
static void unLockResource(volatile DWORD &resourceLock)
{
// _ReadWriteBarrier() is a VC++ intrinsic that generates
// no instructions / only prevents compiler reordering.
// GCC uses __sync_synchronize() or __asm__ ( :::"memory" )
_ReadWriteBarrier();
resourceLock=1;
}
};