内联函数是原子的吗?

时间:2011-01-27 04:25:18

标签: c++ linux inline atomicity

如果以下代码解锁后linux上下文可以切换,如果两个线程调用此

我们会遇到问题
inline bool CMyAutoLock::Lock(
    pthread_mutex_t *pLock,
    bool bBlockOk 
)
throw ()
{
    Unlock();
    if (pLock == NULL)
        return (false);
// **** can context switch happen here ? ****///
    return ((((bBlockOk)? pthread_mutex_lock(pLock) :
        pthread_mutex_trylock(pLock)) == 0)? (m_pLock = pLock, true) : false);
}

5 个答案:

答案 0 :(得分:6)

不,这不是原子的。

事实上,在您解锁互斥锁之后,上下文切换可能特别可能,因为操作系统知道该互斥锁是否阻止了另一个线程。 (另一方面,操作系统甚至不知道您是否正在执行内联函数。)

答案 1 :(得分:4)

内联函数不是自动原子的。 inline关键字仅表示“在编译此代码时,尝试通过使用来自调用主体的汇编指令替换调用来优化调用。”就像在任何其他汇编指令中一样,您可以在任何这些汇编指令上进行上下文切换,因此您需要使用锁来保护代码。

答案 2 :(得分:4)

inline是一个编译器提示,建议编译器将代码内联到调用者而不是使用函数调用语义。但是,这只是一个暗示,并不总是需要注意。

此外,即使注意,结果是您的代码被内联到调用函数中。它不会将您的代码转换为原子序列的指令。

答案 3 :(得分:1)

Inline使函数像宏一样工作。内联与任何方式都与原子无关。

AFAIK inline是一个提示,gcc可能会忽略它。内联时,你的内联函数的代码,称之为B,被复制到调用者函数A中。没有从A到B的调用。这可能会使你的exe更快,但代价是更大。大概?如果你的内联函数很小,exe可能会变小。如果内联使得优化func A变得更加困难,那么exe可能变得更慢。如果你没有指定内联,gcc将在很多情况下为你做出内联决定。类的成员函数是默认的内联。你需要明确告诉gcc不要自动内联。此外,当优化关闭时,gcc不会内联。

链接器不会内联。因此,如果模块A extern引用了标记为内联的函数,但代码在模块B中,则模块A将调用函数而不是内联函数。您必须在头文件中定义函数,并且必须将其声明为extern inline func foo(a,b,c)。 Its actually a lot more complicated.

inline void test(void); // __attribute__((always_inline));

inline void test(void)
{
    int x = 10;
    long y = 10;
    long long z = 10;
    y++;
    z = z + 10;
}
int main(int argc, char** argv)
{
    test();
    return (0);
}

不内联:

!{
main+0: lea    0x4(%esp),%ecx
main+4: and    $0xfffffff0,%esp
main+7: pushl  -0x4(%ecx)
main+10: push   %ebp
main+11: mov    %esp,%ebp
main+13: push   %ecx
main+14: sub    $0x4,%esp
!   test();
main+17: call   0x8048354 <test>    <--- making a call to test.
!   return (0);
main()
main+22: mov    $0x0,%eax
!}
main+27: add    $0x4,%esp
main+30: pop    %ecx
main+31: pop    %ebp
main+32: lea    -0x4(%ecx),%esp
main+35: ret    

内联:

inline void test(void)__attribute__((always_inline));


!   int x = 10;
main+17: movl   $0xa,-0x18(%ebp)                  <-- hey this is test code....in main()!
!   long y = 10;
main+24: movl   $0xa,-0x14(%ebp)
!   long long z = 10;
main+31: movl   $0xa,-0x10(%ebp)
main+38: movl   $0x0,-0xc(%ebp)
!   y++;
main+45: addl   $0x1,-0x14(%ebp)
!   z = z + 10;
main+49: addl   $0xa,-0x10(%ebp)
main+53: adcl   $0x0,-0xc(%ebp)
!}
!int main(int argc, char** argv)
!{
main+0: lea    0x4(%esp),%ecx
main+4: and    $0xfffffff0,%esp
main+7: pushl  -0x4(%ecx)
main+10: push   %ebp
main+11: mov    %esp,%ebp
main+13: push   %ecx
main+14: sub    $0x14,%esp
!   test();                           <-- no jump here
!   return (0);
main()
main+57: mov    $0x0,%eax
!}
main+62: add    $0x14,%esp
main+65: pop    %ecx
main+66: pop    %ebp
main+67: lea    -0x4(%ecx),%esp
main+70: ret  

您可以确定的唯一函数是原子gcc atomic builtins。可能简单的一个操作码汇编指令也是原子的,但它们可能不是。根据我的经验到目前为止,在6x86设置或读取32位整数是原子的。您可以通过查看生成的汇编代码来猜测一行c代码是否可以是原子的。

上面的代码是在32位模式下编译的。您可以看到long long需要加载2个操作码。我猜这不是原子的。 int和longs需要一个操作码来设置。可能是原子的。 y ++是用addl实现的,它可能是原子的。我一直在说,因为cpu上的微码可以使用多个指令来实现操作,并且对此的了解高于我的工资等级。我假设所有32位写入和读取都是原子的。我假设增量不是,因为它们通常用读和写来执行。

但是在64位编译时检查出来

!   int x = 10;
main+11: movl   $0xa,-0x14(%rbp)
!   long y = 10;
main+18: movq   $0xa,-0x10(%rbp)
!   long long z = 10;
main+26: movq   $0xa,-0x8(%rbp)
!   y++;
main+34: addq   $0x1,-0x10(%rbp)
!   z = z + 10;
main+39: addq   $0xa,-0x8(%rbp)
!}
!int main(int argc, char** argv)
!{
main+0: push   %rbp
main+1: mov    %rsp,%rbp
main+4: mov    %edi,-0x24(%rbp)
main+7: mov    %rsi,-0x30(%rbp)
!   test();
!   return (0);
main()
main+44: mov    $0x0,%eax
!}
main+49: leaveq 
main+50: retq   

我猜测addq可能是原子的。

答案 4 :(得分:0)

大多数语句不是原子的。您的线程可能会在++i操作过程中被中断。规则是任何东西都不是原子的,除非它明确定义为原子。