比如说我们定义了一个共享资源dwTime[100]
,两个线程ThreadFuncA
和ThreadFuncB
都对它进行读写操作。当我们想要保证 dwTime[100]
的操作完整性,即不希望写到一半的数据被另一个线程读取,那么用CRITICAL_SECTION
来进行线程同步如下:
第一个线程函数:
DWORD WINAPI ThreadFuncA(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作dwTime
...
LeaveCriticalSection(&cs);
return 0;
}
写出这个函数之后,很多初学者都会错误地以为,此时cs
对dwTime
进行了锁定操作,dwTime
处于cs
的保护之中。一个“自然而然”的想法就是——cs
和dwTime
一一对应上了。
这么想,就大错特错了。dwTime
并没有和任何东西对应,它仍然是任何其它线程都可以访问的。如果你像如下的方式来写第二个线程,那么就会有问题:
DWORD WINAPI ThreadFuncB(LPVOID lp)
{
...
// 操作dwTime
...
return 0;
}
当线程ThreadFuncA
执行了EnterCriticalSection(&cs)
,并开始操作dwTime[100]
的时候,线程 ThreadFuncB
可能随时醒过来,也开始操作dwTime[100]
,这样,dwTime[100]
中的数据就被破坏了。
为了让CRITICAL_SECTION
发挥作用,我们必须在访问dwTime
的任何一个地方都加上 EnterCriticalSection(&cs)
和LeaveCriticalSection(&cs)
语句。所以,必须按照下面的 方式来写第二个线程函数:
DWORD WINAPI ThreadFuncB(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作dwTime
...
LeaveCriticalSection(&cs);
return 0;
}
这样,当线程ThreadFuncB
醒过来时,它遇到的第一个语句是EnterCriticalSection(&cs)
,这个语句将对cs
变量 进行访问。如果这个时候第一个线程仍然在操作dwTime[100]
,cs
变量中包含的值将告诉第二个线程,已有其它线程占用了cs
。因此,第二个线程的 EnterCriticalSection(&cs)
语句将不会返回,而处于挂起等待状态。直到第一个线程执行了 LeaveCriticalSection(&cs)
,第二个线程的EnterCriticalSection(&cs)
语句才会返回, 并且继续执行下面的操作。
这个过程实际上是通过限制有且只有一个函数进入CriticalSection
变量来实现代码段同步的。简单地说,对于同一个 CRITICAL_SECTION
,当一个线程执行了EnterCriticalSection
而没有执行LeaveCriticalSection
的时候,其它任何一个线程都无法完全执行EnterCriticalSection
而不得不处于等待状态。
再次强调一次,没有任何资源被“锁定”,CRITICAL_SECTION
这个东东不是针对于资源的,而是针对于不同线程间的代码段的!我们能够用它来进 行所谓资源的“锁定”,其实是因为我们在任何访问共享资源的地方都加入了EnterCriticalSection
和LeaveCriticalSection
语句,使得同一时间只能够有一个线程的代码段访问到该共享资源而已(其它想访问该资源的代码段不得不等待)。