《Windows核心编程》读书笔记(五)

8章 用户方式中线程的同步

原子访问:互锁的函数家族

线程同步问题在很大程度上与原子访问有关,所谓原子访问,是指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源。

 

比如说对一个全局变量进行自增的操作。需要一种手段来保证值的递增能够以原子操作方式来进行,也就是不中断地进行。


无论编译器怎样生成代码,无论计算机中安装了多少个CPU,它们都能保证以原子操作方式来修改一个值。

还必须保证传递给这些函数的变量地址正确地对齐,否则这些函数就会运行失败

对于互锁函数,需要了解的另一个重要问题是,它们运行的速度极快。调用一个互锁函数
通常会导致执行几个CPU周期(通常小于50),并且不会从用户方式转换为内核方式(通常这需要执行1000CPU周期)。

 

循环锁:

LONGInterlockedExchange(
    PLONGplTarget,
    LONGlValue;
);

InterlockedExchangeInterlockedExchangePointer能够以原子操作方式用第二个参数中传递的值来取代第一个参数中传递的当前值。

BOOL g_fResourceInUse = FALSE;
void Func1()
{
	//Wait to access the resource
	while (InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)
	{
		Sleep(0);
	}
	//Access the resource...

	//no longer need to access the resource
	InterlockedExchange(&g_fResourceInUse, FALSE);		
}

while循环是循环运行的,它将g_fResourceInUse中的值改为TRUE,并查看它的前一个值,以了解它是否是TRUE。如果这个值原先是FALSE,那么该资源并没有在使用,而是调用线程将它设置为在用状态并退出该循环。如果前一个值是TRUE,那么资源正在被另一个线程使用,while循环将继续循环运行。

循环锁会浪费CPU时间。CPU必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。

应该避免在单个CPU计算机上使用循环锁。

 

高级线程同步

一般来说,应该调用一些函数,使线程进入睡眠状态,直到线程需要的资源可供使用为止。

volatile类型的限定词。它告诉编译器,变量可以被应用程序本身
以外的某个东西进行修改,这些东西包括操作系统,硬件或同时执行的线程等。尤其是,volatile限定词会告诉编译器,不要对该变量进行任何优化,并且总是重新加载来自该变量的内
存单元的值。

 

关键代码段

 

const int MAX_TIMES = 1000;
int g_nIndex = 0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;

DWORD WINAPI FirstThread(PVOID pvParam)
{
	while (g_nIndex < MAX_TIMES)
	{
		EnterCriticalSection(&g_cs);
		g_dwTimes[g_nIndex] = GetTickCount();
		g_nIndex++;
		LeaveCriticalSection(&g_cs);
	}
	return 0;
}

DWORD WINAPI FirstThread(PVOID pvParam)
{
	while (g_nIndex < MAX_TIMES)
	{
		EnterCriticalSection(&g_cs);
		g_nIndex++;
		g_dwTimes[g_nIndex - 1] = GetTickCount();
		LeaveCriticalSection(&g_cs);
	}
return 0;
}

编写的需要使用共享资源的任何代码都必须封装在EnterCriticalSectionLeaveCriticalSection函数中。如果忘记将代码封装在一个位置,共享资源就可能遭到破坏。

使用CRITICAL_SECTION结构的2个要求:

第一个要求是,需要访问该资源的所有线程都必须知道负责保护资源的CRITICAL_SECTION结构的地址,

第二个要求是,CRITICAL_SECTION结构中的成员应该在任何线程试图访问被保护的资源之前初始化。该结构通过调用下面的函数来进行初始化:

VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);

EnterCriticalSection函数负责查看该结构中的成员变量。这些变量用于指明当前是哪个变量正在访问该资源。EnterCriticalSection负责进行下列测试:

如果没有线程访问该资源,EnterCriticalSection便更新成员变量,以指明调用线程已被赋予访问权并立即返回,使该线程能够继续运行(访问该资源)。


如果成员变量指明,调用线程已经被赋予对资源的访问权,那么EnterCriticalSection便更新这些变量,以指明调用线程多少次被赋予访问权并立即返回,使该线程能够继续运行。这种情况很少出现,并且只有当线程两次调用EnterCriticalSection而不影响对LeaveCriticalSection的调用时,才会出现这种情况。


如果成员变量指明,一个线程(除了调用线程之外)已被赋予对资源的访问权,那么EnerCriticalSection将调用线程置于等待状态。这种情况是极好的,因为等待的线程不会浪费任何CPU时间。系统能够记住该线程想要访问该资源并且自动更新CRITICAL_SECTION的成员变量,一旦目前访问该资源的线程调用LeaveCriticalSection函数,该线程就处于可调度状态。

 

EnterCriticalSection能够以原子操作方式来执行所有关于资源是否能被访问的测试


可以使用下面这个函数来代替EnterCriticalSection

BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);

TryEnterCriticalSection函数决不允许调用线程进入等待状态。相反,它的返回值能够指明调用线程是否能够获得对资源的访问权。因此,如果TryEnterCriticalSection发现该资源已经被另一个线程访问,它就返回FALSE。在其他所有情况下,它均返回TRUE运用这个函数,线程能够迅速查看它是否可以访问某个共享资源,如果不能访问,那么它可以继续执行某些其他操作,而不必进行等待。

 

在接触共享资源的代码结尾处,必须调用下面这个函数:

VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);
LeaveCriticalSection 要查看该结构中的成员变量。该函数每次计数时要递减 1 ,以指明调用线程多少次被赋予对共享资源的访问权。如果该计数大于 0 ,那么 LeaveCriticalSection 不做其他任何操作,只是返回而已。

关键代码段与循环锁

当线程试图进入另一个线程拥有的关键代码段时,调用线程就立即被置于等待状态。这意
味着该线程必须从用户方式转入内核方式(大约1000CPU周期)。

为了提高关键代码段的运行性能,Microsoft将循环锁纳入了这些代码段。


EnterCriticalSection函数被调用时,它就使用循环锁进行循环,以便设法多次取得该资源。只
有当为了取得该资源的每次试图都失败时,该线程才转入内核方式,以便进入等待状态。
若要将循环锁用于关键代码段,应该调用下面的函数,以便对关键代码段进行初始化:

BOOLInitializeCriticalSectionAndSpinCount(
            PCRITICAL_SECTIONpcs,
            DWORDdwSpinCount;
 );

第二个参数dwSpinCount中,传递的是在使线程等待之前
它试图获得资源时想要循环锁循环迭代的次数。这个值可以是00x00FFFFFF之间的任何数字。

 

关键代码段与错误处理

InitializeCriticalSection函数的运行可能失败(尽管可能性很小)。Microsoft在最初设计该函数时并没有真正想到这个问题,正因为这个原因,该函数的原型才设计为返回VOID。该函数的运行可能失败,因为它分配了一个内存块以便系统得到一些内部调试信息。如果该内存的分配失败,就会出现一个STATUS_NO_MEMORY异常情况。可以使用结构化异常处理来跟踪代码中的这种异常情况。

 

非常有用的提示和技巧

1.每个共享资源使用一个CRITICAL_SECTION变量,可以调高效率

2.同时访问多个资源,注意Enter函数的顺序问题,避免发生死锁

3.不要长时间运行关键代码段

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值