首页 > 编程知识 正文

windows核心编程第15章,windows内核编程第五版

时间:2023-05-03 21:46:16 阅读:150235 作者:3063

临界区是一个小代码段,在执行代码之前可以独占对特定资源的访问。 需要注意的是,系统仍然可以控制线程的执行并调度其他线程。 但是,在线程退出关键节之前,不会调度尝试访问同一资源的其他线程。 让我们看一下代码:

const int p=1000; //操作此全局变量的int g_index=0; DWORD g_time[p]; dwordwinapithread1(dwordpparam ) while(g_indexp ) g _ time (g _ index )=gettickcount ); //系统启动后经过的毫秒数g _ index } return 0; } dwordwinapithread2(dwordpparam ) while ) { { g _ index } g _ index; g_time[g_index - 1]=GetTickCount (; }return 0; }这两个函数单独运行会产生相同的结果; 如果在只有一个处理器的机器上运行,系统可能会首先调用Thread2。 线程执行g_index后,如果没有cpu时间片,则切换到Thread1。 这个线程的GetTickCount )取得时间时,将g_thime[1]设定为系统时间,然后将cpu分配给Thread1,这样时间长的反而设定为0,短的设定为1,预计的结果是我们用临界区解决这个问题:

const int p=1000; //操作此全局变量的int g_index=0; DWORD g_time[p]; CRITICAL_SECTION g_cs; //关键对象dwordwinapithread1(dwordpparam ) while ) g_indexp )企业级) g_cs ); //进入临界区的g_time[g_index]=GetTickCount (; leavecriticalsection(g_cs ),搜索自系统启动以来经过的毫秒数g_index; //离开临界区}返回0; } dwordwinapithread2(dwordpparam ) while(g_indexp ) entercriticalsection (g _ cs ); //进入临界区g_index的g_time[g_index - 1]=GetTickCount (; leavecriticalsection(g_cs; //离开临界区}返回0; 指定保护所有资源的CRITICAL_SECTION结构,并包装EnterCriticalSection和LeaveCriticalSection函数可能共享资源的代码。 两个函数都调用结构的地址。

如果有多个未使用的资源,例如1和2访问一个资源,1和3访问另一个资源,则必须为每个资源创建独立的CRITICAL_SECTION结构。 此结构用于标识需要访问的线程,而EnterCriticalSection用于标识是否正在使用此线程。 退出关键节时,请记住一定要调用LeaveCriticalSection。 否则,其他线程将无法访问资源。

在无法使用互锁函数解决同步问题时,需要用到临界区。临界区的优点是使用容易,在内部使用互锁函数,因此能够快速运行。它的缺点是无法使用它们对多个进程中各个线程进行同步。

了解临界区第一阶段后,进入临界区第二阶段,进入基础理解原理:先从第一个疑点,CRITIACAL_SECTION结构开始。 当mldpy在F1中看到这个结构时,只能看到结构成员,不知道成员来自哪里。 因为微软认为你不需要理解这个结构。 CRITICAL_SECTION在WinNT.h中被定义为RTL_CRITICAL_SECTION。 此结构也是在WinBase中定义的,但不应该编写引用这些成员的代码。

使用CRITIACAL_SECTION结构需要windows函数,该函数如何处理结构的成员?

使用此结构有两个要求。

需要访问此资源的线程必须知道负责保护线程的CRITICAL_SECTION结构的我的地址。 这个地址可以用任何方法取得。 CRITICAL_SECTION结构中的成员必须先初始化成员,然后才能访问。 函数是voidiniticlizecriticalsection (pcritical _ section PCs ); 由于此函数只是初始化了结构的一些成员,因此执行不会失败。 如果线程进入未初始化的CRITICAL_SECTION结构,则结果不可预测。

如果线程不需要访问资源,则必须调用函数来明确CRITICAL_SECTION结构: voiddeletecriticalsection (pcritionpcs )。

前面说过EnterCriticalSecion怎么使用,现在说一下为什么这么使用:

trong>这个函数负责查看这个结构体中的成员变量,然后进行如下测试:后面为了方便这个函数使用ECS函数代替;

如果没有线程访问资源,ECS就更新成员变量。告诉线程能够单独访问这个资源。如果成员变量指明线程已经被赋予对资源的访问权,ECS就更新成员变量,说明线程被赋予了多少次访问权并且立即返回,使现车个继续运行。这种情况很少见,只有当线程在一行中调用俩次ECS函数并且不影响LeaveCriticalSection函数的调用,才会出现这种情况。如果成员变量指明,这个资源在被调用之前就有别的线程获取了访问权,那ECS将调用线程置于等待状态。等待线程不会浪费cpu。当这个资源调用了LeaveCritiolSecton释放资源后,这个线程就会从等待状态恢复为可调度状态。

有一种极端情况,如果在多处理器上俩个线程在同一时刻调用ECS函数,那这个函数还有用吗?  答案是有用,还是会将一个线程赋予资源访问权,有一个线程进入等待。因为这个函数的所有测试操作都是以原子方式进行的。

如果ECS函数将一个线程置于等待状态,要是在编写不好的程序中这个线程永远不会被调用,这个线程被称为渴求线程。但是在实际操作中,永远也不会出现这种情况。在注册表中CriticalSectionTimeout数据值决定的。如果请求时间超过这个时间,就会产生一个异常条件。这个函数其实可以用更方便的一个函数来代替:BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);

这个不允许进入等待状态,它的返回值能够指明调用线程是否能够获取资源的访问权。如果发现有别的线程在访问资源,句返回FALSE,其他条件都会返回TRUE。要注意的是这个函数在windows 98中并没有实现,调用总会返回FALSE。

再来认识一个函数:WaitForSingleObject。

当WaitForSingleObject函数的第一个参数从未通知状态别为已通知状态时,在这个函数之下的WaitForSingleObject就不会在等待;线程正在运行为未通知状态,反正为已通知;这个函数等待单个对象,WaitForMultipleObjects()函数等待多个对象;第三个参数如果是true,会等待所有线程都执行完才会往下跑,如果是false,只要有任何一个线程变为已通知就会往下跑;

再来看在结尾处需要调用的函数LeaveCriticalSection函数的使用:这个函数没调用一次计数就会减1,如果这个计数大于0,那么这个函数不做其他操作,只返回。如果为0,就会查看在EnterCriticalSection中是否有其他线程在等待,如果至少有一个线程在等待,它就会先更新成员变量,将其中一个线程变为可调度状态。如果没有线程在等待,这个函数也会更新成员变量说明情况。

LeaveCriticalSection函数和EnterCriticalSection函数一样都可以以原子操作执行所有这些测试和更新,不过LeaveCriticalSection从不会使线程进入等待状态。当线程进入等待状态时,意味着线程必须从用户模式转为内核状态,这种转换消耗巨大。

在来看临界区的另外一种情况:在内存不足的情况下,可能会争临界区,同时系统也无法创建必要的事件内核对象,这是EnterCriticalSection会产生一个EXCEPTION_INVALID_HANDLE异常,这种情况非常少见,有俩种方法可以对这种情况进行处理。

可以使用结构化异常处理方式来跟踪错误。当初五发生时,可以不访问临界区保护的资源,可以等待某些内存变为可用状态时,再次调用EnterCriticalSection函数。可以使用InitializeCriticalSectionAndSpinCount函数创建代码段,函数解释可以在vs中选中按F1进行查看,该函数要确保设置了dwSpinCount参数的高位。如果设置了,就创建事件内核对象。并且在初始化时和临界区关联起来。如果事件无法创建,就返回FALSE。如果事件创建成功,那EnterCriticalSection函数始终都能够运行。

 

关于临界区的使用技巧:

1.每个共享资源使用一个CRITICAL_SECTION变量

DWORD g_time[100];DWORD g_name[100];CRITICAL_SECTION g_cs;//创建临界区对象DWORD WINAPI Thread1(DWORD pParam) {EnterCriticalSection(&g_cs);for (int i = 0; i < 100; i++) {g_name[i] = 0;}for (int i = 0; i < 100; i++) {g_time[i] = 'x';}LeaveCriticalSection(&g_cs);return 0;}

这段代码在理论上是讲,俩个数组初始化没有联系,在初始化数组g_name后,另一个只需要访问g_name数组而不是访问g_time数组的线程就可以执行了,同时Thread1可以继续对g_time数组进行初始化,但是这是不可能的,因为用一个临界区保护着这俩个数据结构。这种情况就需要创建俩个临界区分别初始化:

DWORD g_time[100];CRITICAL_SECTION g_csTime;//创建临界区对象DWORD g_name[100];CRITICAL_SECTION g_csName;//创建临界区对象DWORD WINAPI Thread1(DWORD pParam) {EnterCriticalSection(&g_csTime);for (int i = 0; i < 100; i++) {g_name[i] = 0;}LeaveCriticalSection(&g_csTime);EnterCriticalSection(&g_csName);for (int i = 0; i < 100; i++) {g_time[i] = 'x';}LeaveCriticalSection(&g_csName);return 0;}

这个代码一旦完成了对g_name数组的初始化,另一个线程就可以开始使用g_name数组。

2.同时访问多个资源

DWORD WINAPI Thread1(DWORD pParam) {EnterCriticalSection(&g_csTime);EnterCriticalSection(&g_csName);for (int i = 0; i < 100; i++) {g_name[i] = g_time[i];}LeaveCriticalSection(&g_csName);LeaveCriticalSection(&g_csTime);return 0;}

如果另外一个函数中的一个进程也要访问这俩个资源:

DWORD WINAPI Thread2(DWORD pParam) {EnterCriticalSection(&g_csName);EnterCriticalSection(&g_csTime);for (int i = 0; i < 100; i++) {g_name[i] = g_time[i];}LeaveCriticalSection(&g_csName);LeaveCriticalSection(&g_csTime);return 0;}

这个函数切换了进入临界区的顺序,就有可能产生死锁。Thread1先获得g_csTime的所有权,当线程切换到Thread2时,先获得了g_csName的所有权,当cpu再次到thread1的时候,就会发生死锁,谁都无法获得另一个临界区的所有权。

解决这个问题,必须始终按照完全相同的顺序请求对资源的访问。

3.不要在临界区长时间运行同一个线程

如果无法确定在处理消息需要花费多长时间,可能几个毫秒,可能需要几年。这样的程序就是有问题的。

 

 

 

 

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。