windows 核心编程(用户模式下的线程同步)

1、线程之间需要相互通信

 

    (1)需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性

    (2)一个线程需要通知其他线程某项任务已经完成

 

2、原子访问:Interlocked系列函数

 

    Interlocked函数时如何工作的呢?答案取决于代码运行的CPU平台。如果是X86系列CPU,那么Interlocked函数会在总线上维持一个硬件信号,这个信号会阻止其他CPU访问同一个内存地址。

    注意:Interlocked函数能够保证对值的修改时以原子方式进行的。但是我们必须确保传给这些函数的变量地址是经过对齐的,否则这些函数可能会失败。

    Interlocked函数执行得极快。调用一个Interlocked函数通常只占用几个CPU周期,而且也不需要再用户模式和内核模式之间进行切换(这种切换很耗时)。

    从windows xp开始,除了能对整数或布尔值进行这些原子操作外,我们还能使用一系列其他的函数来对一种称为Interlocked单向链表的栈进行操作。栈中的每个操作,比如入栈或出栈,必定是以原子的方式进行的。

 

3、高速缓存行

 

    如果想为装备有多处理器的机器构建高性能应用程序,那么就应该注意高速缓存行。当CPU从内存中读取一个字节的时候,它并不只是从内存中取回一个字节,而是取回一个高速缓存行。高速缓存行的大小根据CPU的不同而不同,高速缓存行存在的目的是为了提高性能。一般来说,应用程序会对一组相邻的字节进行操作。如果所有字节到在高速缓存中,那么CPU就不必访问内存总线。

    但是,在多处理器环境中,高速缓存线使得对内存的更新变得更加困难。(每个CPU都有自己的高速缓存行)

   

    我们应该根据高速缓存行的大小来将应用程序的数据组织在一起,并将数据与缓存行的边界对齐。这样做的目的是为了确保不同的CPU能够各自访问不同的内存地址,而且这些地址不在同一个高速缓存行中。

    此外,我们应该把只读数据与可读写数据分贝存放。我们还应该把差不多会在同一时间访问的数据组织在一起。

 

    最好是始终只让一个线程访问数据(使用函数参数和局部变量),或者始终只让一个CPU访问数据(使用线程关系),这样就能完全避免高速缓存行的问题。

 

4、高级线程同步

 

    如果只需要以原子方式修改一个值,那么Interlocked系列函数非常好用,我们应该优先使用它们。

    但是大多数时候我们处理的数据结构往往要比一个简单的32位值或64位值复杂的多,所以我们转而使用windows提供的一些其他特性。

    (当线程想要访问一个共享资源或者想要得到一些“特殊事件”的通知时,线程必须调用操作系统的一个函数,并把线程正在等待的东西作为参数传入。如果操作系统检测到资源已经可供使用了,或者特殊事件已经发生了,那么这个函数会立即返回,这样线程将仍然保持可调度状态;

     如果无法取得对资源的访问权,或者特殊事件的发生,那么系统会将线程切换到等待状态,使线程变得不可调度,从而避免让线程浪费CPU时间。当线程在等待的时候,系统会充当它的代理。系统会记住线程想要访问什么资源,当资源在等待的时候,它会自动将线程唤醒)

 

    注意:如果没有同步对象,如果操作系统不能对特殊事件进行检测,那么线程将不得不用下面介绍的技术来在自己和特殊事件之间进行同步。::::就是使用轮询的方法(还要注意使用volatile关键字)。

 

5、关键段

 

    关键段是一段小代码,它在执行之前需要独占对一些共享资源的访问权。即对多行代码以“原子方式”来对资源进行操控。在当前线程离开关键段之前,系统是不会去调度任何想要去访问同一资源的其他线程。

    当不能用Interlocked函数解决同步问题的时候,我们应该试一试关键段。关键段的最大好处是它们非常容易使用,而且它们在内部也使用了Interlocked函数,因而执行速度非常快。关键段的最大缺点是:它们无法用来在多个进程之间对线程进行同步。

 

    EnterCriticalSection会检查结构中的成员变量,这些变量表示是否有线程正在访问资源,以及那个线程正在访问资源:

    (1)如果没有线程正在访问资源,那么EnterCriticalSection会更新成员变量,以表示调用线程已经获准对资源的访问,并立即放回。

    (2)如果成员变量表示调用线程已经获准访问资源,那么EnterCriticalSection会更新变量,以表示调用线程被获准访问的次数,并立即返回(这种情况非常少见,只有当调用LeaveCriticalSection之前连续调用EnterCriticalSection两次以上才会发生)

    (3)如果成员变量表示有一个(调用线程之外的其他的)线程已经获准访问资源,那么EnterCriticalSection会使用一个事件内核对象来把调用线程切换到等待状态。

    注意:如果EnterCriticalSection把一个线程切换到等待状态,那么在很长一段时间内系统可能不会去调度这个线程。如果发生这种情况,我们说线程在挨饿。实际的情况是,等待关键段的线程时绝对不会挨饿的,对EnterCriticalSection的调用最终会超时并引发异常。

 

    当线程试图进入一个关键段,但这个关键段正被另一个线程占用的时候,函数会立即把调用线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式,这个切换的开销非常大。在多处理器的机器上,这种会存在一个大问题(当前占用资源的线程可能在另一个处理器上运行,而且可能很快就会结束对资源的访问。事实上,在需要等待的线程完全切换到内核模式之前,占用资源的线程可能就已经释放了资源。如果发生这种情况,那户浪费大量的CPU时间)

    所以为了提高关键段的性能,Microsoft把旋转锁合并到了关键段中。

 

6、Slim读/写锁

 

    SRWLock的目的和关键段相同:对一个资源进行保护,不让其他线程访问它。但是,与关键段不同的是,SRWLock允许我们区分那些想要读取资源的值的线程和想要更新资源的值的线程。让所有的读取者线程在同一时刻访问共享资源应该是可行的,这是因为仅仅读取资源的值并不存在破坏数据的风险。只有当写入者线程想要对资源进行更新的时候才需要进行同步;这种情况下,写入者线程应该独占对资源的访问。

 

   

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值