WINDOWS下线程同步探讨

原创 2003年04月08日 14:27:00

本文主要讨论WINDOWS应用层编程的线程同步问题。在实际编程过程中,我们经常会遇到线程同步的问题,例如在编写多线程共同访问一个共享资源的程序时,如果多个线程只是读取资源那么就不会涉及到下面我们要讨论的问题;如果当有的线程读取资源,有的线程改变资源时,就会产生资源在访问时的同步问题。即当一个线程改变资源,同时其他线程也在读写该资源,这样会导致资源内容的不可确定性。为避免这种情况的发生,保证资源的完整性,我们通常会采用线程同步的方法。WINDOWS操作系统为我们提供了事件EVENT)、信号量SEMAPHORE)、互斥MUTEX)等内核对象来同步线程,同时也提供了关键代码段CRITICAL_SECTION)这样的用户方式来进行同步。下面我就对这两种情况分别来讨论。

       内核对象方式:WINDOWS内核提供的可以用来进行线程同步的内核对象,都是可以发信号(Signaled)的对象,在WINDOWS中它们被称为“调度程序对象”(dispatcher object)。它们包括进程PROCESS)、线程THREAD)、事件EVENT)、信号量SEMAPHORE)、互斥MUTEX)、时钟TIMER)等。对这一类的对象,线程都可以通过用WaitForSingleObject()WaitForMultpleObjects()API来获得对象的使用权。当对象处于信号态时,线程立即返回,获得对象的使用权。当对象未处于信号态时,即对象正被其他线程占用时,线程就会被排在该内核对象的等待队列当中,当对象变为信号态时,队列中排队的第一个线程便得到该对象,被内核放入线程就绪队列,已重新获得CPU时间。在线程使用完对象后,需要释放该对象的使用权,以便其它等待该对象的线程获得执行。相关对象操作的APIMSDN中有详细的描述,这里就不再复述。下面简单讨论一下,WINDOWS的内核实现:

相关结构

typedef struct _DISPATCHER_HEADER {

    UCHAR Type;

    UCHAR Absolute;

    UCHAR Size;

    UCHAR Inserted;

    LONG SignalState;

    LIST_ENTRY WaitListHead;

} DISPATCHER_HEADER;

 

typedef struct _KWAIT_BLOCK {

    LIST_ENTRY WaitListEntry;

    struct _KTHREAD *RESTRICTED_POINTER Thread;

    PVOID Object;

    struct _KWAIT_BLOCK *RESTRICTED_POINTER NextWaitBlock;

    USHORT WaitKey;

    USHORT WaitType;

} KWAIT_BLOCK, *PKWAIT_BLOCK, *RESTRICTED_POINTER PRKWAIT_BLOCK;

(注:这两个结构包含NTDDK.H中,是公开的)

       在下面的图(1)中我们看到,线程1在等待对象B,线程2在等待对象AB。如果对象A变为信号状态,内核将会检测到线程2在等待该对象,但由于线程2还在等待对象B,那么线程2还不能被放入线程就绪队列,以重新获得CPU。如果对象B变为信号

                                                        图(1

状态,那么线程1由于没有等待其它对象,而被内核放入就绪队列并执行。而线程2则需等待线程1释放对象B后才可被安排获得执行。对于不同的内核对象,内核在处理等待它们的线程时也不尽相同,对于进程线程等内核对象,当对象变为信号状态时,内核将一次使所有等待线程都获得使用权。要对此详细了解的朋友可以看《Inside Windows 2000》。

 

用户方式:WINDOWS下,WINDOWS还给用户提供了关键代码段CRITICAL_SECTION),这种用户方式的线程同步解决方法。这种方式,比内核对象方式执行快。因为每次检测内核对象是否被占用时,系统都要由用户模式换为核心模式,这样就要消耗不少的切换时间。而关键代码段只是在用户模式下被简单检测是否已被其它线程占用,这样就比内核对象少了模式间切换所用的时间。在需要同步保护的代码量不是很大的情况下,使用关键代码段不失为一个好的解决方法,它可以提高代码的执行效率,减少线程间的冲突。当关键代码段发生冲突时,即一个线程检测到关键代码段已被其他线程占用时,该线程就进入等待状态。关键代码段有一个等待超时,如果超时发生时,线程仍未获得关键代码段,就会产生一个异常。所以,在编写关键代码段程序时应当注意用完后释放。关键代码段的超时值被记录在注册表的HKEY_LOCAL_MACHINE/System/CurrentControSet/Control/Session Manager键下的CriticalSectionTimeout关键字中。默认的值为2592000s,大约30天。我们可以通过改动这个值来调整超时时间,由于该值是系统公用的,所以建议设置该值时最小不要小于3S,以免影响到系统正常等待关键代码段超过3S的线程和其他应用。具体操作关键代码段API,在MSDN中都有详细描述,这里也不进行复述。 下面简单讨论一下线程同步实现的原理:

操作系统之所以能够进行线程同步,主要依靠的是原子操作,原子操作是不会被打断的操作,通常它是由处理器体系结构提供,一般是由硬件支持的test-and-set操作。下面是笔者在X86体系架构下,效仿LINUX写的两个WINDOWS函数,TestAndSetBit()用于将指定的位设为1TestAndClearBit()用于将指定的位清0,即加锁与解锁操作。

static int  TestAndSetBit(int nOrder,volatile   void*      Var)

{

       int    nOldBit=0;

      

       _asm {

       MOV EAX,Var

       MOV      EBX,nOrder

       LOCK BTS [EAX],EBX       

       MOV      EAX,nOldBit  

       SBB nOldBit,EAX

       }

       return nOldBit;

}

 

static int  TestAndClearBit(int nOrder,volatile void*     Var)

{

       int    nOldBit=0;

 

       _asm {

       MOV EAX,Var

       MOV      EBX,nOrder

       LOCK BTR [EAX],EBX

       MOV      EAX,nOldBit  

       SBB nOldBit,EAX

       }

       return nOldBit;

}

(注:LOCK指令是锁总线,用于多处理器的情况。BTSX86test-and-set操作。BTRX86test-and-reset操作)

下面通过一段代码用上面的函数来实现线程同步:

DWORD g_dwLock=0;

while(TestAndSetBit(0,&g_dwLock))

       Sleep(1000);

/*

要保护的代码

*/

TestAndClearBit(0,&g_dwLock);

这是一个用户模式下的线程同步的应用,冲突发生时,当前线程睡眠,以使获得锁的线程能够运行被保护的代码,并在运行结束后解锁。当等待线程睡醒并重新执行时,就会得到锁并进行后面的运行。在核心模式下,原理是一样的,只是在资源冲突时的处理方式不同。

 

       具体编程时要用哪一种方式进行线程的同步还要看程序的具体需要,笔者这里就不列举了。最后,笔者要向大家道歉,文章写的有些乱,可能有些地方讲的不清楚,有些地方理解的不对,还望大家及时指出。

 

参考书目:

Inside windows 2000

WINDOWS内核编程》

 

 

                                                                             colorknight

                                                                             2003/4/3

windows线程同步的总结

一 线程 1)如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用VisualC++运行期库函数_beginthreadex,退出也应该使用_endthreadex...
  • peter_teng
  • peter_teng
  • 2013年09月06日 15:57
  • 6839

线程同步(5):windows下各种锁

windows下的线程同步方式有以下几种: 用户方式下的:原子锁,关键代码段                                                             ...
  • wentianyao
  • wentianyao
  • 2016年05月13日 17:41
  • 2685

Windows 内核模式下的线程同步

鉴于我的食言,这个月无法完成一些探索和文章的工作了,把以前研究的一点小东西...
  • YahooLucas
  • YahooLucas
  • 2014年06月24日 17:22
  • 427

Windows下C++多线程同步与互斥简单运用

继以往的想法,写这点文字,粘贴点代码,是为了增加自己的记忆,也希望能帮助到需要帮助的人。1.  互斥量,Mutex#include #include using namespace st...
  • ccing
  • ccing
  • 2011年03月01日 17:19
  • 12873

Linux3种线程同步机制的封装

Linux3种同步机制的封装
  • qisefengzheng
  • qisefengzheng
  • 2015年06月17日 11:41
  • 320

windows下线程同步

windows下线程同步windows下的一些同步方法,volatile、Interlocke函数、CRITICAL_SECTION、SRWLOCK、Mutex。 - volatile: 变量声明...
  • yangyang031213
  • yangyang031213
  • 2017年07月19日 16:17
  • 122

Windows 下的线程同步

这段时间一直在做Windows下的项目开发, 最近花了点时间翻了翻书, 总结一下Windows常用的线程同步方法. 一、线程同步--SendMessage() 1. SendMessage()是一种...
  • DennisZhangX
  • DennisZhangX
  • 2010年07月21日 11:36
  • 594

Windows下的线程同步

总结一下Windows下的线程同步方法。 首先是WaitForSingleObject函数,每个方法都用到了。 DWORD WaitForSingleObject(   HANDLE hHandl...
  • hjf161105
  • hjf161105
  • 2017年02月11日 20:33
  • 101

windows与linux线程同步对比

1.1        Windows线程同步 1.1.1   关键代码区Critical Section 所谓“关键代码区”,相信大家看名字也能理解个大概了。首先:它很关键,第二:它是代码区。之...
  • peter_teng
  • peter_teng
  • 2016年10月10日 16:02
  • 954

windows 使用关键段和条件变量实现的生产者和消费者线程同步

关键段比较简单,调用函数如下: VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );//初始化一个关键代码段...
  • xiaoyafang123
  • xiaoyafang123
  • 2016年12月06日 10:52
  • 506
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:WINDOWS下线程同步探讨
举报原因:
原因补充:

(最多只允许输入30个字)