常用的Interlocked系列函数

来源:互联网 发布:python try finally 编辑:程序博客网 时间:2024/05/21 06:24
因此在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。这种涉及到硬件的操作会不会很复杂了,幸运的是,Windows系统为我们提供了一些以Interlocked开头的函数来完成这一任务(下文将这些函数称为Interlocked系列函数)。

下面列出一些常用的Interlocked系列函数:

1.增减操作

LONG__cdeclInterlockedIncrement(LONG volatile* Addend);

LONG__cdeclInterlockedDecrement(LONG volatile* Addend);

返回变量执行增减操作之后的值

LONG__cdecInterlockedExchangeAdd(LONG volatile*Addend,LONGValue);

返回运算后的值,注意!加个负数就是减。

 

2.赋值操作

LONG__cdeclInterlockedExchange(LONG volatile* Target,LONGValue);

Value就是新值,函数会返回原先的值。

 

在本例中只要使用InterlockedIncrement()函数就可以了。将线程函数代码改成:

DWORD WINAPI ThreadFun(void *pPM){Sleep(100);//some work should to do//g_nLoginCount++;InterlockedIncrement((LPLONG)&g_nLoginCount);Sleep(50);return 0;}
 
可以使用这些函数实现多线程同步:
假设 g_x++ 这样的C语句操作需要如下的汇编指令:
?
1
2
3
MOV EAX, [g_x]
INC EAX
MOV [g_x], EAX

可能执行完第二句指令,新的g_x值还没有回写内存,线程的时间片到了,控制权交给了另外一个线程的,另一个线程也要操作g_x,那么结果将是不可预知的。

可见线程同步的难度似乎比我们想象的要大一些。幸好,Windows或各种语言或者各种类库为我们提供了很多线程同步的方法。这篇开始讨论Win32下的线程同步的话题。

 

原子访问:Interlocked系列函数

为了解决上面对g_x++这样的操作的原子访问(即保证g_x++不会被打断),可以用如下方法:

?
1
2
3
4
5
6
7
8
9
10
long g_x = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam){
    InterlockedExchangeAdd(&g_x,1);
    return(0);
}
  
DWORD WINAPI ThreadFunc2(PVOID pvParam){
    InterlockedExchangeAdd(&g_x,1);
    return(0);
}

上面代码的InterlockedExchangeAdd保证加法运算以“原子访问”的方式进行。InterlockedExchangeAdd的工作原理根据不同的CPU会有所不同。但是,我们必须保证传给这些Interlocked函数的变量地址是经过对齐的。

所谓对齐,是指数据的地址模除数据的大小应该为0,比如WORD的起始地址应该能被2整除,DWORD的地址能被4整除。x86架构的CPU能够自动处理数据错位,而IA-64的处理器不能处理,而会将错误抛给Windows,Windows能决定是抛出异常还是帮助CPU处理错位。总之,数据错位不会导致错误,但由于CPU将至少多耗费一个读内存操作,因此将影响程序的性能。

InterlockedExchange用于以原子的方式设置一个32位的值,并返回它之前的值,可以用来实现旋转锁(spinlock):

?
1
2
3
4
5
6
7
8
9
10
11
12
//全局变量指示共享资源是否被占用
BOOL g_fResourceInUse = FALSE;
...
void Func1(){
    //等待共享资源释放
    while ( InterlockedExchange ( &g_fResourceInUse, TRUE ) == TRUE )
        Sleep(0);
    //访问共享资源
    ...
    //不再需要共享资源时释放
    InterlockedExchange ( &g_fResourceInUse, FALSE );
}

 

while循环不停的进行,并且设置g_fResourceInUse为TRUE,如果返回值为TRUE表示资源已经被占用,于是线程Sleep(0)意味着线程立即放弃属于自己的时间片,这样将导致CPU调度其他线程。如果返回值为FLASE,表示资源当前没有被占用,可以访问共享资源。不过在使用这项技术的时候要很小心,因为旋转锁将浪费CPU时间。

 
关于告诉缓存和volatite:
CPU拥有高速缓存,CPU高速缓存的大小是评判CPU性能的一个指标。现如今的CPU一般拥有3级的缓存,CPU总是优先从一级缓存中中读取数据,如果读取失败则会从二级缓存读取数据,最后从内存中读取数据。CPU的缓存由许多缓存行组成,对于X86架构的CPU来说,高速缓存行一般是32个字节。当CPU需要读取一个变量时,该变量所在的以32字节分组的内存数据将被一同读入高速缓存行,所以,对于性能要求严格的程序来说,充分利用高速缓存行的优势非常重要。一次性将访问频繁的32字节数据对齐后读入高速缓存中,减少CPU高级缓存与低级缓存、内存的数据交换。

但是对于多CPU的计算机,情况却又不一样了。例如:

  1. CPU1 读取了一个字节,以及它和它相邻的字节被读入 CPU1 的高速缓存。
  2. CPU2 做了上面同样的工作。这样 CPU1 , CPU2 的高速缓存拥有同样的数据。
  3. CPU1 修改了那个字节,被修改后,那个字节被放回 CPU1 的高速缓存行。但是该信息并没有被写入RAM 。
  4. CPU2 访问该字节,但由于 CPU1 并未将数据写入 RAM ,导致了数据不同步。

当然CPU设计者充分考虑了这点,当一个 CPU 修改高速缓存行中的字节时,计算机中的其它 CPU会被通知,它们的高速缓存将视为无效。于是,在上面的情况下, CPU2 发现自己的高速缓存中数据已无效, CPU1 将立即把自己的数据写回 RAM ,然后 CPU2 重新读取该数据。 可以看出,高速缓存行在多处理器上会导致一些不利。

以上背景知识对于我们编程至少有如下两个意义:

1、有些编译器会对变量进行优化,这种优化可能导致CPU对变量的读取指令始终指向高速缓存,而不是内存。这样的话,当一个变量被多个线程共享的时候,可能会导致一个线程对变量的设置始终无法在另一个线程中体现,因为另一个线程在另一个CPU上运行,并且变量的值在该CPU的高速缓存中!volatile关键字告诉编译器生成的代码始终从内存中读取变量,而不要做类似优化。

2、在多CPU环境下,合理的设置高速缓存对齐,以使得CPU之间的高速缓存同步动作尽量的少发生,以提升性能。要对齐高速缓存,首先要知道目标CPU的高速缓存行的大小,然后用__declspec(align(#))来告诉编译器为变量或结构设置指定符合高速缓存行大小的数据大小,例如:

?
1
2
3
4
struct CACHE_ALIGN S1 { // cache align all instances of S1
   int a, b, c, d;
};
struct S1 s1;   // s1 is 32-byte cache aligned

更多内容可参见:http://msdn.microsoft.com/en-us/library/83ythb65.aspx

 

具体的,高速缓存行对齐的目标可以是:在结构中,把经常读操作的字段和经常写操作的字段分开,使得读操作的字段与写操作的字段出现在不同的高速缓存行中。这样就减少了CPU高速缓存行同步的次数,一定程度上提升了性能。

 
下面是关于原子操作的两个类和原子加操作的类,可以参考一下:
//code
 

#include <Windows.h>

#include <intrin.h>

 

class SpinLock

{

public:    

    SpinLock()

        : v_(0)

    {}

   

    void Lock()

    {        

        while (::InterlockedCompareExchange(&v_, 1, 0) == 1)

        {

            _mm_pause(); //

        }

    }

   

    void UnLock()

    {

        ::InterlockedCompareExchange(&v_, 0, 1);

    }

 

private:

    long v_;

  };

 

void yield(unsigned k)

{

    if (k < 4)

    {     }

    else if (k < 16)

    {

        _mm_pause();

    }

    else if (k < 32)

    {

        ::Sleep(0);

    }

    else

    {

        ::Sleep(1);

    }

}

class spinlock

{

public:

    spinlock() : v_(0)

    {     }

    bool try_lock()

    {

        long r = InterlockedExchange(&v_, 1);

        _ReadWriteBarrier();   // 这个起嘛作用::内存壁垒

        return r == 0;

    }

    void lock()

    {

        for (unsigned k = 0; !try_lock(); ++k)

        {

            yield(k);

        }

    }

    void unlock()

    {        

        _ReadWriteBarrier();

        *const_cast<long volatile*>(&v_) = 0;  // const_castvolatile

    }

private:

    long v_;

};

template<class IntType>

class AtomicAdd

{

public:

    explicit AtomicAdd(IntType iValue) : m_iValue(0)

    {

        m_iValue = iValue;

    }

   

    IntType Add()

    {

        InterlockedIncrement(&m_iValue);

    }

 

    void RollBack()

    {

        InterlockedDecrement(&m_iValue);

    }

   

    IntType GetValue()

    {

        return m_iValue;

    }

private:

    IntType m_iValue;

};

0 0
原创粉丝点击