开发者

Is an inline function atomic?

开发者 https://www.devze.com 2023-02-07 12:14 出处:网络
Can linux context switch after unlock in the below code if so we have a problem if two threads call this

Can linux context switch after unlock in the below code if so we have a problem if two threads call this

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);
}


No, it's not atomic.

In fact, it may be especially likely for a context switch to occur after you've unlocked a mutex, because the OS knows if another thread is blocked on that mutex. (On the other hand, the OS doesn't even know whether you're executing an inline function.)


Inline functions are not automatically atomic. The inline keyword just means "when compiling this code, try to optimize away the call by replacing the call with the assembly instructions from the body of the call." You could get context-switched out on any of those assembly instructions just as you could in any other assembly instructions, and so you will need to guard the code with a lock.


inline is a complier hint that that suggests that the compiler inline the code into the caller rather than using function call semantics. However, it's just a hint, and isn't always heeded.

Futhermore, even if heeded, the result is that your code gets inlined into the calling function. It doesn't turn your code into an atomic sequence of instructions.


Inline makes a function work like a macro. Inline is not related to atomic in any way.

AFAIK inline is a hint and gcc might ignore it. When inlining, the code from your inline func, call it B, is copied into the caller func, A. There will be no call from A to B. This probably makes your exe faster at the expense of becomming larger. Probably? The exe could become smaller if your inline function is small. The exe could become slower if the inlining made it more difficult to optimize func A. If you don't specify inline, gcc will make the inline decision for you in a lot of cases. Member functions of classes are default inline. You need to explicitly tell gcc to not do automatic inlines. Also, gcc will not inline when optimizations are off.

The linker wont inline. So if module A extern referenced a function marked as inline, but the code was in module B, Module A will make calls to the function rather than inlining it. You have to define the function in the header file, and you have to declare it as 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);
}

Not Inline:

!{
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:

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  

The only functions you can be sure are atomic are the gcc atomic builtins. Probably simple one opcode assembly instructions are atomic as well, but they might not be. In my experience so far on 6x86 setting or reading a 32 bit integer is atomic. You can guess if a line of c code could be atomic by looking at the generated assembly code.

The above code was compile in 32 bit mode. You can see that the long long takes 2 opcodes to load up. I am guessing that isn't atomic. The ints and longs take one opcode to set. Probably atomic. y++ is implemented with addl, which is probably atomic. I keep saying probably because the microcode on the cpu could use more than one instruction to implement an op, and knowledge of this is above my pay grade. I assume that all 32 bit writes and reads are atomic. I assume that increments are not, because they generally are performed with a read and a write.

But check this out, when compiled in 64 bit

!   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   

I'm guessing that addq could be atomic.


Most statements aren't atomic. Your thread may be interrupted in the middle of an ++i operation. The rule is that anything is not atomic unless it's specifically, explicitly defined as being atomic.

0

精彩评论

暂无评论...
验证码 换一张
取 消