用户模式下的线程同步
在以下两种基本情况下,线程之间需要相互通信
1.需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性
2.一个线程需要通知其他线程某项任务已经完成。
原子访问相关的内容就直接略过了,因为感觉实际使用的过程中并不多。
下面直接开始说一下关键段,它在执行之前需要独占对一些共享资源的访问权。这种方式可以让多行代码以原子方式来对资源进行操控。这里的原子方式,指的是代码知道除了当前线程之外,没有其他任何线程会同时访问该资源。在当前线程离开关键段之前,系统是不会去调度任何想要访问同一资源的其他线程的。
一般是EnterCriticalSection和LeaveCriticalScetion配对使用,需要先创建一个CRITICAL_SECTION结构。
这结构一般都是作为全局变量来使用,也可以作为局部变量来分配或者堆中,另外作为类的一个私有字段还分配也是很常见的。
在使用CRITICAL_SECTION的时候,只有两个必要条件,第一个是所有想要访问资源的线程必须知道用来保护资源的CRITICAL_SECTION结构的地址,第二个是任何线程试图访问被保护的资源之前,必须对CRITICAL_SECTION结构的内部成员进行初始化。
下面的函数用来进行初始化
void InitializerCriticalSection(PCRITICAL_SECTION pcs);
当线程不在需要访问共享资源的时候,应该调用下面的函数来清理结构
void deleteCriticalSection(PCRITICAL_SECTION pcs);
EnterCriticalSection会检查结构体中的成员变量,这些变量表示是否有线程正在访问资源,以及哪个线程正在访问资源。EnterCriticalSection会执行下面的测试:
1.如果没有线程正在访问资源,那么EnterCriticalSection会更新成员变量,以表示调用线程已经获准对资源的访问,并立即访问,这样线程就可以继续执行
2.如果成员变量表示调用线程已经获准访问资源,那么EnterCriticalSection会更新变量,以表示调用线程被获准访问的次数,并立即放回,这样线程就可以继续执行。这样的情况非常少见,只有当线程调用LeaveCriticalSection之前连续调用EnterCriticalSection两次以上才会发生。
3.如果成员变量表示有一个(调用线程之外的其他)线程已经获准访问资源,那么EnterCriticalSection会使用一个事件内核对象来吧线程切换到等待状态。一旦当前正在访问资源的线程调用了LeaveCriticalSection,系统会自动更新结构的成员变量并将等待中的线程切换回可调度状态。
我们也可以用下面的函数来代替EnterCriticalSection
Bool TryEnterCriticalSection(PCRITICAL_SECTION pcs);
TryEnterCriticalSection从来不会让调用线程进入等待状态,他会通过返回值来表示调用线程是否获准访问资源,因此如果TryEnterCriticalSection发现资源正在被其他线程访问,那么它会返回false。每一个返回true的TryEnterCriticalSection调用必须有一个对应的LeaveCriticalSection。
当线程试图进入一个关键段,但这关键段正在被另一个线程占用的时候,函数会立即把调用线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式,这个切换的开销非常大。为了提高性能,可以将旋转锁合并到关键段中,因此当调用EnterCriticalSection的时候,他会用一个旋转锁不断的循环,尝试在一段时间内获取的对资源的访问权,只有当尝试失败的时候买线程才会切换到内核状态并进到等待状态。
为了在是因关键段的时候同时使用旋转锁,必须调用以下的函数来初始化关键段
Bool InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs,DWORD dwSpinCount);
与InitializerCriticalSection相似,但是第二个参数是我们希望旋转锁循环的次数,我们也可以通过下面的函数来改变关键段的旋转次数
DWORD WINAPI SetCriticalSectionSpinCount(_Inout_ LPCRITICAL_SECTION lpCriticalSection,_In_ DWORD dwSpinCount
);
用来保护进程对的关键段所使用的旋转次数大约是4000,这里作为参考。
SRWLock的目的和关键段系统,对一个资源进行保护,不让其他资源访问他。但是与关键段不同,SRWLock允许我们区分哪些想要读取资源的值的线程(读取者线程)和想要更新资源的值的线程(写入者线程)。让所有的读取者线程在同一时刻访问共享资源应该是可行的,只有当写入者线程需要对资源进行更新的时候才需要同步,在这样子情况下,写入者线程应该独占对资源的访问权:任何其他线程。
首先我们需要分配一个SRWLOCK结构并用InitialSRWLock函数来对他进行初始化
VOID WINAPI InitializeSRWLock( _Out_ PSRWLOCK SRWLock );
一旦SRWLock的初始化完成后,写入者线程就可以调用AcquireSRWLockExclusive,将SRWLOCK对象的地址作为参数传入,以尝试获得对被保护资源的独占访问权。
VOID WINAPI AcquireSRWLockExclusive( _Inout_ PSRWLOCK SRWLock );
在完成对资源的更新之后。应该调用ReleaseSRWLockExclusive函数,并将SRWLOCK对象的地址作为参数传入,解除对资源的锁定。
VOID WINAPI ReleaseSRWLockExclusive( _Inout_ PSRWLOCK SRWLock );
读取者的操作
读取者调用的两个参数是:
VOID AcquireSRWLockShared(PSRWLOCK SRWLock);
VOID ReleaseSRWLockShared(PSRWLOCK SRWLock);
不存在删除或销毁SRWLock的函数,系统会自动执行清理工作。
与关键段相比,SRWLock缺乏下面两个特性:
(1)不存在TryEnter(Shared/Exclusive)SRWLock之类的函数。如果锁已经被占用,那么调用AcquireSRWLock(SHared/Exclusive)会阻塞调用线程。
(2)不能递归调用SRWLOCK。一个线程不能为了多次写入资源而多次锁定资源,然后多次调用ReleaseSRWLock来释放对资源的锁定。
但是,如果可以接受这些限制,就可以用SRWLock来代替关键段,并获得实际性能和可伸缩性的提升。
三、同步机制性能的比较
通过一个简单的基准测试可以比较各种同步机制的性能:产生1、2和4个线程,使用不同的同步机制重复执行相同的任务,在双处理器上运行,得出的结果是:
用户模式下同步机制性能的对比实验结果如下(计数单位:微秒)
线程数 |
Volatile Read |
Volatile Write |
Interlocked Increment |
Critical Section |
SRWLock Shared |
SRWLock Exclusive |
Mutex |
1 |
8 |
8 |
35 |
66 |
66 |
67 |
1060 |
2 |
8 |
76 |
153 |
268 |
134 |
148 |
11082 |
4 |
9 |
145 |
361 |
768 |
244 |
307 |
23785 |
各种机制对比:
(1)读取volatile长整型值,读取非常快,因为不需要进行任何同步,与CPU的高速缓存完全无关。
(2)写入volatile长整型值。单线程的时间和读取差不多,但是双线程的时候时间不只是加倍,这是因为CPU之间必须相互通信以维护高速缓存的一致性。如果机器有更多的CPU,那么性能还会下降,因为需要在更多的CPU之间进行通信来使得所有CPU的高速缓存一致。
(3)使用InterlockedIncrement来安全递增一个volatile长整型值。
它比第一种方法要慢,这是因为CPU必须锁定内存。使用两个线程要比一个线程慢得多,这是因为必须在两个CPU之间来回传输数据以维护高速缓存的一致性。
(4)使用关键段来读取一个volatile长整型值。
关键段比较慢,是因为我们必须先进入再离开。进入和离开需要修改CRITICAL_SECTION结果中的多个字段。4个线程需要花费更多时间,是因为上下文切换增大了发生争夺现象的可能性。
(5)使用SRWLock来读取一个volatile长整型值。
当有多个线程的时候,读操作比写操作快。由于多个线程会不断地写入锁的字段以及它保护的数据,因此各CPU必须在它们的高速缓存之间来回传输数据。
它的性能和关键段差不多,但是很多时候要优于关键段。建议的做法是用SRWLock替代关键段。
(6)使用同步内核对象互斥量。
互斥量是目前性能最差的,是因为等待互斥量以及后来释放互斥量需要线程每次在用户模式和内核模式之间却换,开销很大。
总结:应该首先尝试不要共享数据,然后依次使用volatile读取、volatile写入、Interlocked函数、SRWLock以及关键段。仅当这些都不能满足要求的时候,再使用内核对象。
Windows提供SleepConditionVariableCS或SleepConditionVariableSRW函数,等待条件变量。线程在等待该条件变量时,会以原子方式把锁释放并将自己阻塞,直到该条件变量被触发时为止。
Bool SleepConditionVariableCS( PCONDITION_VARIABLE pConditionVariable, PCRITICAL_SECTION pCriticalSection, DWORD dwMilliseconds); Bool SleepConditionVariableSRW( PCONDITION_VARIABLE pConditionVariable, PSRWLOCK pSRWLock, DWORD dwMilliseconds ULONG Flags);
pConditonVariable指向一个以初始化的条件变量,调用线程将等待该条件变量。第二个参数指向一个关键段或是SRWLock对象。该关键段或SRWLock用来同步对共享资源的访问。Flags指定一旦条件变量被触发,线程将以何种方式获得锁。对读取者线程来说应该传入CONDITION_VARIABLE_LOCKMODE_SHARED表示希望共享对资源的访问。对于写入者线程应该传入0,表示独占资源。
dwMilliseconds表示我们希望线程花多少时间来等待条件被触发。在指定的时间用完时,如果条件变量尚未被触发,函数返回false,否则为true。
当另一个线程检测到相应的条件已经满足时,比如存在一个元素可以让读取者线程读取。它会调用WakeConditionVariable或WakeAllConditionVariable,触发条件变量。这样调用Sleep*函数而阻塞在该条件变量的线程就会被唤醒。
Void WakeConditonVariable( PCONDITION_VARIABLE ConditionVariable);
Void WakeAllConditionVariable( PCONDITION_VARIABLE ConditionVariable);
WakeConditionVariable会使SleepConditionVariable*等待的同一个条件变量被触发的线程得到锁并返回。当此线程释放这个锁的时候,不会唤醒其他正在等待此条件变量的线程。
用户模式下的线程同步相关推荐
- Windows用户模式下的线程同步
Interlocked系列函数 原子访问:线程在访问某个资源的时候能保证没有其他线程会在同一时刻访问同一资源 函数名 功能 InterlockedExchangeAdd InterlockedExch ...
- Chapter09-内核模式下的线程同步之事件内核对象
有两种事件内核对象:自动事件和手动事件.当手动事件被触发时,所以该事件的等待线程都编程可调度状态:而自动事件被触发时,只有个一个等待该事件线程变成可调度状态. 下面再逐个讲解Event的相关函数: a ...
- 内核和用户模式下进程与线程创建
文章目录 内核模式下进程与线程的创建 进程创建 线程创建 用户模式下进程与线程的创建 内核模式下进程与线程的创建 进程创建 在内核模式中,一个进程的创建是从函数NtCreateProcess开始的.该 ...
- linux7启动某个服务器,如何在单用户模式下启动RHEL 7 CentOS 7服务器
对于Linux系统管理员,以单用户模式引导RHEL 7 / CentOS 7服务器是最常见的日常活动.单用户模式被视为维护或紧急模式,我们可以执行我们的故障排除步骤.以下是我们需要在单用户模式下启动R ...
- linux单用户模式修复磁盘,在单用户模式下使用fsck命令修复受损的Mac硬盘
在Mac上使用磁盘工具来恢复硬盘是官方推荐的方法,不过万一连系统都进不去就操蛋了.所以在很多情况下,在Unix/Linux系统的单用户模式下使用fsck都是最后的救命稻草. fsck 这个命令行工具在 ...
- linux 返回非法指令,linux – ARM Cortex A7在内核模式下返回PMCCNTR = 0,在用户模式下返回非法指令(即使在PMUSERENR = 1之后)...
我想在Raspberry Pi 2上读取循环计数寄存器(PMCCNTR),它有一个ARM Cortex A7内核.我为它编译了一个内核模块,如下所示: #include #include int in ...
- 在单用户模式下启动SQL Server的不同方法
In this article, we will review different ways to start SQL Server in single user mode. 在本文中,我们将介绍在单 ...
- CentOS6.8单用户模式下修改密码
CentOS6.8单用户模式下修改密码 1. 选择进入菜单menu界面,在开启系统出现如下界面时,按Esc键(只需按一下) 2. 然后进入到如下界面 3. 上图中红色矩形类的内容,按"a&q ...
- 【Linux下】 线程同步 生产者与消费者模型
文章目录 [Linux下] 线程同步 生产者与消费者模型 线程同步 同步概念与竞态条件 条件变量 条件变量本质 操作条件变量 初始化和销毁条件变量 等待 唤醒 通过条件变量实现的简单线程同步例子 为什 ...
最新文章
- lr手工添加关联函数的步骤:
- pyav Invalid data found when processing input (libav.h264: no frame!)
- python400集视频教程 百度云-Python自动化测试视频教程【百度云盘下载】
- Win32 鼠标绘图代码研究
- 命令行启动Angular应用
- python去重复功能_消除Python列表重复的几种方法,python,去,一些
- stc单片机485发送多出一字节_单片机干货!STC8H案例制作分享(内含高清实物动图)...
- j.u.c系列(08)---之并发工具类:CountDownLatch
- java socket 线程池_java socket编程的一个例子(线程池)
- lex yacc 入门教程(3)正则表达式和lex变量及函数
- 4.在屏幕上输出以下图案: * *** ***** ******* ********* *********** ************* *********** *********
- MQTT服务器的搭建与MQTT客户端的使用
- ImageMagick图片转PDF
- Mac蓝牙无法使用怎么办?教你7个修复蓝牙的技巧
- win10官方iso下载
- MATLAB中help的使用
- Android 7.1.1源码下载
- Linux重置root 密码
- 猫眼api html,python爬取动态数据实战---猫眼专业版-实时票房(二)
- 正则表达式?:代表什么意思
热门文章
- r语言 xmlto html,使用R语言将XML转换为CSV(示例代码)
- ​Cell:粟硕/施莽团队利用宏转录组揭示“野味”动物携带和人类疾病密切相关的多种病毒...
- 引号吃掉了我的数据~~~
- word2vec相似度计算_干货|文本相似度计算
- awk 匹配_linux的awk命令详解,通俗易懂
- selenium java封装_selenium2.0的初步封装(java版本)
- tail -f 查找关键字_C语言九种查找算法 | 总有一款适合你
- 信息安全工程师笔记-网络安全主动防御技术与应用
- Java笔记-JDK搭建WebService客户端其他调用方法
- Flask笔记-使用flask-sqlacodegen自动生成model