Windows几种线程同步方法介绍

摘要:
LeaveCriticalSection将检查结构CRITICAL_ SECTION的成员变量和计数器将减1。如果计数器变为0,LeaveCriticalSection将更新成员变量,以指示没有线程访问资源。如果存在等待线程,则等待线程将切换到可调度状态。SRWLock的目的与关键段的目的相同,即保护资源不受其他线程的影响。

系统中的所有线程都要访问系统资源,一个线程霸占某个资源,其他需要该资源的线程就不能完成自己的任务;另外如一个线程在读取某块内存中的数据,而另一个线程又正在修改这块内存的值,这同样不是我们想要的,所以线程之间必须要有一套自己的规则,不然就凌乱了。线程之间需要通信,如A线程霸占某个B线程需要的资源X,在A占用期间,B线程只能等待,或处于挂起状态,当A线程用完资源X后,系统会告诉线程B,资源X可以用了,或是将处于挂起状态的线程B唤醒,然后线程B就获得对资源X的控制权,其他想用资源X的线程就得经历B刚才的遭遇。当多个线程同时需要某个资源时必须遵守下面两个规则:

1:多个线程“同时”访问资源,不能破坏资源的完整性。

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

原子访问:Interlocked系列函数。多线程编程大部分情况与原子访问有关,即一个线程在访问某个资源时,确保没有其他线程能访问该资源。

增量函数InterlockedExchangeAdd结构如下:

InterlockedExchangeAdd(

    unsigned long volatile *Addend,//被增量变量的地址

    unsigned long Value//增量值

)

Volatile表示每次都成内存中读取数据,而不会从高速缓存中读取数据,如一个全局变量,在一个多线程函数中被修改,在多核CPU中,这个变量可能在多个CPU的高速缓存中都有副本,如果不用volatile修饰,那么可能会因为优化的原因,CPU不会读内存中的数据,而是直接从高速缓存中读取数据,在这种情况下,很可能这个值已经被修改了,这样CPU读取到的不是最新的数据,程序肯定会出错,用volatile修饰后,这个变量的所有高速缓存就会失效,就不会出现这种问题。在多线程编程中volatile作用非常大,效率也最高。但他就是只能修饰单个变量,不能修饰代码段。

InterlockedExchangeAdd执行的速度是非常快的,只需要占用几个CPU周期。用InterlockedExchangeAdd来修改某个变量的值,好像有点大材小用了,因为用Volatile就足够了,简单迅速。但在实现旋转锁时InterlockedExchange就非常有用。旋转锁的代码大致如下:

        bool sourceIsUse=false;
        void fun()
        {
            //一直等待直到资源可用
            while(InterlockedExchange(&sourceIsUse,true)==true)
            {
                Sleep(0);
            }
            //访问资源的操作
            ......

            //资源用好了,打开锁,让其他等待的资源访问
            InterlockedExchange(&sourceIsUse,false);
        }

InterlockedExchange:将第一个参数的值修改成第二个参数的值,返回第一个参数原来的值。在第一个线程就来的时候,它顺利的闯过了While循环,并上了锁,导致while始终为true,后来的线程就一直在while里面打转,当前面的线程用完之后,他就会把锁打开,然后新来的线程就可以跳出while循环,并上锁(在等待时一直在上锁),开锁独占资源了,新来的线程又开始等待。就像大厦前门的旋转门,一拨人进去之后,后面的人就只能在外面等,等里面的人出去之后,后面的人也就可以进去了,周而复始。

高速缓存行。当CPU从内存中读取一个字节时,它并不是真的只读一个字节,而是读取一个高速缓存行,一个高速缓存行可能是32个字节、64个字节或是128个字节,它始终读取的字节数是32的整数倍,这样CPU就不用非常频繁的读取内存,从而提高程序的性能,当CPU访问某块内存是它会访问这块内存旁边的内存的概率是非常大的,于是就一起读了。更多关于数据对齐的信息请看我的文章数据对齐》。

高级线程同步。刚刚简单的说了一下旋转锁,现在又来说旋转锁的坏,旋转锁的问题在于等待的线程一直在执行毫无用处的该死的死循环,浪费CPU的时间,这肯定是不能容忍的,虽然曾经一度容忍过它。当一个线程需要某个资源,而这个资源被另一个线程占用时,如果这个线程等了一会儿还不能获得这个资源,那么这个线程就应该被切换到等待状态,让系统充当该线程的代理,当该资源可以被使用时,系统就会将该线程唤醒,然后该线程就可以独占该资源。而实现这一功能的就是关键段。

关键段。关键段是一小段代码,在执行之前需要独占对一些共享资源的访问,这种方式可以让多行代码以原子的方式进行访问,当有一个线程对访问这段代码时其他线程只能等待。使用关键段的步骤如下:

CRITICAL_SECTION g_cs;//构造一个CRITICAL_SECTION实例

InitializeCriticalSection(&g_cs);//初始化g_cs的成员

EnterCriticalSection(&g_cs);//进入关键段

LeaveCriticalSection(&g_cs);//离开关键段

DeleteCriticalSection(&g_cs);//清理g_cs

EnterCriticalSection会检查结构CRITICAL_SECTION的成员变量,这些成员表示是否有线程正在访问资源,以及哪个线程正在访问资源,EnterCriticalSection会进行一些测试。如果没有线程正在访问资源,EnterCriticalSection会更新变量成员,以表示已经有线程正在访问资源,并马上从EnterCriticalSection返回,继续执行关键段中的代码,如果变量成员表示已经有线程正在访问资源,那么EnterCriticalSection会使用一个事件内核对象把线程切换成等待状态,等待状态的线程是不会浪费CPU的时间的,系统会记住这个线程想要使用这个资源,一旦当前线程调用LeaveCriticalSection,系统会自动更新CRITICAL_SECTION的成员变量,并将等待的线程切换成可调度状态。

LeaveCriticalSection会检查结构CRITICAL_SECTION的成员变量并将计数器减一,如果计数器变为0,LeaveCriticalSection会更新成员变量表示现在没有线程访问资源,若有等待的线程,则将等待的线程切换成可调度的状态。

当一个线程进入关键段时,若有线程正在访问关键段,那么系统就会将新的线程切换成等待状态,这意味着将线程从用户模式切换成内核模式,这个切换的开销大约是1000个CPU周期,这个开销其实是很大的,所以在EnterCriticalSection内部使用旋转锁,并不是马上将线程切换成等待状态,而是先用旋转锁试探一些,看线程是否释放了对资源的访问,如果释放了,新的线程就不用被切换成等待状态了,就可以直接访问资源了,也就是说花了旋转锁轮询的时间,如果旋转锁轮询了一段时间,线程还是没有释放资源,对不起系统就不会让它继续轮询了,因为系统也不知道还要轮询多久,毕竟轮询一直都是在消耗CPU的时间,系统会停止轮询,将新的线程切换成等待状态,当前一个资源释放对资源的访问,系统会将新的线程切换成可调度状态。

Silm读/写锁。SRWLock的目的和关键段是一样的,就是对资源的保护,不让其他线程访问。不同的是,它区分线程是读线程还是写线程。我们都是知道,一个资源可以同时被多个线程同时读,就是不能同时读,或是读写。也是是说写必须是独占的方式,而读可以以共享的方式访问。

读写锁调用的函数如下,跟关键段差不多,我就不废话了。

RTL_SRWLOCK lock;

InitializeSRWLock(&lock);

AcquireSRWLockExclusive(&lock);//独占的方式访问

ReleaseSRWLockExclusive(&lock);

AcquireSRWLockShared(&lock);//共享的方式访问

ReleaseSRWLockShared(&lock);

Windows几种线程同步方法介绍第1张

Windows线程基础

Windows内核对象简介

作者:陈太汉

博客:http://www.cnblogs.com/hlxs/

免责声明:文章转载自《Windows几种线程同步方法介绍》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ndarray多维数组HTTP、MQTT、Websocket、WebService区别下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【原创】Spring连接、事务代码分析

1.JdbcTemplate 当不使用事务时,jdbcTemplate的模板类,通过 Connection con = DataSourceUtils.getConnection(getDataSource()); 方法,首先去当前线程的上下文中寻找绑定的数据库连接,若没找到,则新建一个连接,即从DataSource中创建一个新的连接: Connecti...

见到的一篇IOCP流程 自己用demo实现了一下, 简单照抄,改动了一点点

要分析的实例分为两个线程: 分别是主线程(MAIN),还有一个是创建的线程(ServerThread) 1.主函数完成初始化工作:   1.1: (主线程)HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);    创建完成端口对象   1.2: (主线程...

网络通信框架Apache MINA

Apache MINA(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。当前发行的 MINA 版本支持基于 Java NIO 技术的TCP/UDP 应用程序开发、串口通讯程序。Mina 的应用层:一个...

uboot完全手册---14

1. u-boot介绍 本次移植采用的是U-Boot-1.2.0版本。 3. U-Boot源码分析 3.1 源码入口的解释 可能大多数的同学上网查资料后都了解到,stage1阶段的启动代码,主要就在start.s文件里。此start.s也是系统上电后执行的第一个代码。它全部由汇编编写。在讲述start.s之前,我们先来了解一下,系统怎么知道它要先去star...

MySQL锁定状态查看命令

1 show processlist; SHOW PROCESSLIST显示哪些线程正在运行。您也可以使用mysqladmin processlist语句得到此信息。如果您有SUPER权限,您可以看到所有线程。否则,您只能看到您自己的线程(也就是,与您正在使用的MySQL账户相关的线程)。如果有线程在update或者insert 某个表,此时进程的stat...

SQLServer2008-2012资源及性能监控—CPU使用率监控具体解释

前言:CPU是server中最重要的资源。在数据库server中,CPU的使用情况应该时刻监控以便SQLServer一直处于最佳状态。 本文将会使用可靠性和性能监视器来获取CPU相关的使用统计信息 可靠性和性能监视器是过去性能监视器工具的加强版。同一时候拥有性能监视器的所有功能。 性能计数器提供对各种系统活动的统计功能。能够找到有数百种性能计数器来针对W...