Windows多线程基础(6):线程间通信1(互锁函数、CriticalSection)

来源:互联网 发布:酷音铃声mac版本 编辑:程序博客网 时间:2024/04/30 10:58
一个进程内多个线程共享一份内存空间,因此多个线程拥有对各种系统资源的访问权,例如内存堆栈、串口、文件、窗口和其他许多资源。但是又不能让任何一个线程在任何时间都能访问所有的资源,例如对于同一块内存,一个线程从该内存读取数据,另一个线程却将数据写入该内存,如不加限制,该内存块的数据就会变得乱七八糟了。

线程需要在进行通信的场景:
  • 当有多个线程访问共享资源而不使资源被破坏时。
  • 当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。
1.原子访问:互锁的函数家族
原子操作是指不会被线程调度机制打断的操作,所谓原子访问,是指线程在访问资源时就能确保其他线程都不在同一时刻访问相同的资源。

以下介绍几种原子操作的函数:
  • InterlockedExcnahgeAdd函数
LONG InterlockedExchangeAdd( PLONG plAddend, LONG lIncrement);
    • 该函数能够将一个长变量(*plAddend)以lIncrement的值递增,这个函数能够保证值以原子方式来完成。
    • 需要注意的另一个问题是效率:互锁函数的运行速度极快,通常会导致执行几个CPU周期,并且不会从用户态进入内核态(通常这个需要执行1000个CPU周期)
    • 另外,该函数返回在*plAddend中的原始值
  • InterlockedExchange函数、InterlockedExchangePointer函数
LONG InterlockedExchange(PLONG plTarget, LONG lValue);PVOID InterlockedExchangePointer(PVOID* ppvTarget, PVOID pvValue);
    • 这两个函数能够以原子操作的方式用第二个参数中传递的值来取代第一个参数中传递的当前值。
    • 若是32位应用程序,两个函数都能用另一个32位值来取代一个32位值。但是,若是64位应用程序,则前者能够取代32位值,而后者能够取代64位值。
    • 两个函数都返回原始值。
当实现一个循环锁时,InterlockedExchange是非常有用的:
//定义一个全局变量,用来保证共享资源是否正在被使用中BOOL g_fResourceInUse = FALSE;void Funcl{    // 等待访问共享资源    while (InterlockedExchange( &g_fResourceInUse, TRUE) == TRUE)        Sleep(0);    // 使用资源    InterlockedExchange( &g_fResourceInUse, FALSE);}</span>
  1. while循环将g_fResourceInUse中的值改为TRUE,并通过查看他的前一个值,以检查它是否为TRUE。如果这个值是FALSE,那么该资源并没有在使用,则InterlockedExchange函数将值改为TRUE并退出该循环。若前一个值是TRUE,那么表示资源正在被另一个线程使用,while循环继续循环运行,继续等待资源被使用完毕。
  2. 有点像自旋锁?当使用这个方法时必须格外小心,因为循环锁会浪费CPU时间,CPU必须不断地比较两个值,直到一个值被另一个线程改变为止。
  3. while循环中,sleep(0)表示当前线程放弃剩余CPU时间片。为了避免CPU时间的浪费,sleep可能休眠一个CPU时间的可能会更加好一些;或许在多次请求访问该资源均被拒绝时,适当进一步延长睡眠时间是一个选择。
  4. 对于循环锁,需要假定受保护的资源总是被访问较短的时间,这样能够更加有效地循环运行,然后再转入内核方式并进入等待状态。依据经验,循环一定的次数(400次),如果对资源的访问仍然被拒绝,那么该线程就转为内核方式,在内核方式下,它要等待(不占用CPU时间),直到该资源变为可供使用位置。这就是CriticalSection实现的方法。
  • 最后两个互锁函数InterlockedCompareExchange、InterlockedCompareExchangePointer
PVOID InterlockedCompareExchange(     PLONG plDestination,     LONG lExchange,     LONG lComparand);PVOID InterlockedCompareExchangePointer(     PVOID* ppvDestination,     PVOID pvExchange,     PVOID pvComparand);

    • 这两个函数负责执行一个原子测试和设置操作。若是32位程序,两个函数都在32位值上运行,若是64位程序,前者运行在32位值上,后者运行在64位值上。

没有互锁函数仅仅对值进行读取操作,而不去改变这个值的,这样的函数是不需要的。
若果线程使用一个变量,而这个变量始终都由互锁函数来修改,那么这个变量总能确保线程安全。

2.CRITICAL_SECTION,临界区
CRITICAL_SECTION(下面简称CS)是指一段代码,在代码能够执行前,线程必须独占对某些共享资源的访问权,这是让一段代码能够以原子操作方式来使用资源的一种方法。

首先我们看一下CS的结构(这个结构只是帮我们理解用的,我们也不应该直接访问CS结构体中的成员):
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION ;typedef struct _RTL_CRITICAL_SECTION {    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;    //    //  The following three fields control entering and exiting the critical    //  section for the resource    //    LONG LockCount;             //初始化为-1,n表示有n个线程在等待    LONG RecursionCount;        //关键段的拥有线程对此资源锁定的次数,初始为0    HANDLE OwningThread;        //拥有该关键段的线程的句柄    HANDLE LockSemaphore;    ULONG_PTR SpinCount;        //在线程进入等待之前,线程试图获得资源时想要循环锁循环迭代的次数} RTL_CRITICAL_SECTION, * PRTL_CRITICAL_SECTION ;
  • 通常,CS结构可以作为全局变量来分配,则所有线程能够很容易的引用该结构。
以下是使用该结构所用到的系统函数:
  • VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
    • 使用CRITICAL_SECTION结构之前,必须对齐初始化。
  • VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
    • 用于清除不再使用的CRITICAL_SECTION
  • VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
    • 如果没有被锁定,负责查看该结构的成员变量,若没有线程访问资源,则锁定资源。
    • 在已锁定的情况下,同一个线程再次调用该函数,则CS结构体中变量RecursionCount被更新,并立即返回。
    • 在已锁定的情况下,另一线程调用该函数后,则调用线程被置于等待状态。直到锁定线程调用LeaveCriticalSection函数后,处于等待的线程就进入可调度状态。
  •  VOID TryEnterCriticalSection(PCRITICAL_SECTION pcs);
    • 该函数不会让调用线程进入等待状态,若返回FALSE,表示该资源已被另一线程访问。返回TRUE,则CS结构的成员变量已经被更新,以便反映出线程正在访问该资源。需要调用LeaveCriticalSection函数释放。
  • VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);
    • 该函数使CS的计数递减1,已指明调用线程多少次锁定CS的访问权。如果该计数大于0,那么该函数不做其他操作,只是返回而已。
    • 若果计数变为0,需要查看在调用EnterCriticalSection中是否有别的线程正在等待,若至少有一个线程正在等待,他就更新成员变量,并使等待线程中的一个线程再次处于可调度状态。
    • 若果没有线程正在等待,则更新CS的成员变量,以指明没有线程访问该资源。
  • VOID InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs, DWORD dwSpinCount);
    • 线程被置于等待状态,意味着线程必须从用户方式进入内核方式,这种转换需要付出很大代价。若用于资源的线程可在另一线程完成转入内核方式之前释放资源,就会浪费许多CPU时间。
    • 为了提高CS的运行性能,将循环锁纳入了这些代码段。当EnterCriticalSection函数被调用时,使循环锁进入循环,以便设法多次取得该资源。只有当每次都失败时,该线程才进入内核方式,以便进入等待状态。
使用CS时的提示和技巧:
  • 每个共享资源使用一个CS变量
  • 同时访问多个资源,需要对两个或者两个以上的CS加锁的时候,需要注意死锁问题
  • 不要长时间持有CS

0 0
原创粉丝点击