⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 15.4.3 利用互斥对象实现线程同步.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 TXT
📖 第 1 页 / 共 2 页
字号:
循环之后,调用WaitForSingleObject函数,但这时该互斥对象己经被线程l所拥有,处于未通知状
态,线程2没有获得互斥对象的所有权,因此WaitForSingleObject函数就会处于等待状态,从而导
致线程2处于暂停执行状态。当线程1的睡眠时间到了之后,线程I将会继续执行,即销售一张火车票。
这时线程1调用ReleaseMutex函数释放互斥对象的所有权,也就是让该对象处于已通知状态。如果这
时轮到线程 2执行了,那么该线程的 WaitForSingleObject函数就可以得到互斥对象的所有权,线
程2继续执行下面的代码。同样地,当线程2销售了一张票之后,也通过调用ReleaseMutex函数,释
放它对互斥对象的所有权。
我们可以把互斥对象看成是一把房间钥匙,只有得到这把钥匙后,我们才能进入这个房间,完成应
做的工作。当我进入房间关上门后,因为钥匙在我手上,其他人拿不到该钥匙,因此就无法进入这
个房间,只能等待。只有等我离开这个房间并交出钥匙,其他人才能进入该房间,完成应做的工作,
最后离开房间,交出钥匙。 
Build并运行MultiThread程序,将会发现这时所销售的票号正常,没有看到销售了号码为O的票。这
就是通过互斥对象来保存多线程间的共享资源,本例是保护对全局变量的访问,使得当其中一个线
程访问该资源时,其他线程不能访问同一种资源。
读者应注意这时 WaitForSingleObject函数的调用位置,如果我们把如例 15-3所示代码中的两个线
程函数中调用 WaitForSingleObject函数的代码放到 while循环之前,并把 ReleaseMutex函数的调
用放在while循环结束之后(即这时线程1入口函数的代码如例15-4所示,线程2入口函数的代码作相
应变化),这时程序会出现什么情况呢?
例 15-4 

DWORD WINAPI FunlProc( LPVOI D lpPararneter // thread data 
WaitForSingleObject (hMutex, INFIN工TE} ; 
while(TRUE} i f (tickets>O} Sleep(l) ; cout<<"threadl sell ticket : "<<tickets--<<endl; 
else 
break; 
ReleaseMutex(hMutex) ; 
return 0; 

读者可以试着运行这时的 MultiThread程序,结果将会看到火车票的销售工作是没有问题的,但是
发现这时只是线程l在销售票,对于线程2来说,没有看到它销售任何一张 
票。我们可以分析这时的程序执行过程,当线程 1开始运行时,它就调用 WaitForSingleObject函
数请求互斥对象,由于这时互斥对象处于有信号状态,线程 1可以请求到该对象,因此继续执行,
进入 while循环,当执行到 Sleep函数时,它会暂停执行:于是,线程 2开始执行,它也调用 
WaitForSingleObject函数请求互斥对象,但是该互斥对象当前已被线程 1所拥有,于是线程 2请求
不到该对象的所有权,线程 2只能等待。当线程 1暂停时间到了之后,将继续执行,销售一张票。
之后它又进入下一次循环。也就是说,该互斥对象始终被线程 1所拥有,线程 1将在 while循环内
部不断地销售火车票,直到所有的 100张火车票都被卖完之后,线程 1才会退出 while循环,调用 
ReleaseMutex函数释放对互斥对象的所有权,这时,线程 2才能获得该互斥对象的所有权,继续执
行下面的代码,但是该线程判断出票号已经不大于 0了,因此就没有执行 if语句下的代码,直接退
出 while循环,调用 ReleaseMutex函数释放互斥对象的所有权,线程 2结束。于是,程序的结果就
是线程 2没有销售一张票。通过本例是想提醒读者,一定要注意调用 WaitForSingleObject函数的
位置。
下面仍以例 15-3代码为例,如果在创建互斥对象时,将第二个参数设置为 TRUE,然后再次运行 
MultiThread程序,结果将会看到线程 1和线程 2都没有销售票。我们来分析这时的程序执行过程。
当调用 CreateMutex函数创建互斥对象时,如果将第二个参数设置为 TRUE,表明创建互斥对象的线
程,本例即主线程,拥有该互斥对象,而我们在主线程中并没有释放该对象,因此对于线程 1和线
程 2来说,它们是无法获得该互斥对象的所有权的。它们只能等待,直到主线程结束,才会释放该
互斥对象的所有权,但这时两个线程 也已退出了。
那么如果我们这样做:在线程函数的 while循环内部,在调用 WaitForSingleObject函数之前,先调
用 ReleaseMutex函数释放互斥对象的所有权,之后,再调用 WaitForSingleObject函数请求该互斥
对象的所有权。这时线程 1和线程 2能否得到该互斥对象的所有权呢?读者可以试着按照此方法修改
代码,然后再次运行 MultiThread程序,将会看到线程 1和线程 2仍未得到销售火车票的机会。下
面分析出现这种情况的原因。
对于互斥对象来说,它是惟一与线程相关的内核对象。当主线程拥有互斥对象时,操作系统会将互
斥对象的线程 ID设置为主线程的ID。当在线程 1中调用 ReleaseMutex函数释放互斥对象的所有权
时,操作系统会判断线程 1的线程 ID与互斥对象内部所维护的线程 D是否相等,只有相等才能完成
释放操作。正因为上述实现方法中,释放互斥对象的线程与互斥对象内部所维护的线程 ID不相等,
所以该互斥对象并没有被释放。当然接下来请求该互斥对象的所有权操作就只能一直等待,因此线
程 1和线程 2都没有执行 if语句下的代码,从而就没有看到线程售票的信息。
也就是说,对互斥对象来说,谁拥有谁释放。知道了这一原则,我们就可以这么做,在主线程中,
当调用 CreateMutex创建了互斥对象之后,调用 ReleaseMutex函数释放主线程对该互斥对象的所有
权。这时主线程的代码如例 15-5所示。
例 15-5 
void main ( )
 HANDLE hThread1; 

HANDLE hThread2; 
11创建线程 
hThread1=CreateThread(NULL , 0,Fun1Proc ,NULL , 0,NULL) ; 
hThread2=CreateThread(NULL , 0,Fun2Proc ,NULL , 0,NULL) ; 
CloseHandle(hThread1) ; 
CloseHandle(hThread2) ; 

11创建互斥对象 
hMutex=CreateMutex(NULL , TRUE , NULL) ; 
ReleaseMutex(hMutex) ; 
Sleep(4000) ; 
读者可以再次运行MultiThread程序,这时将会看到线程1和线程2交替销售火车票了。说明这两个线
程得到了互斥对象的所有权,从而执行了if语句下的代码。
对本程序的编写,还有一种情况需要说明,就是在主线程中,当调用 CreateMutex函数创建互斥对
象之后,调用WaitForSingleObject函数请求该互斥对象(即在上述如例 15-5所示 main函数中调用 
CreateMutex函数的代码之后添加下面这条语句),然后再调用 ReleaseMutex函数释放主线程对该互
斥对象的所有权,这时程序的结果会是怎样的呢? 
WaitForSingleObject (hMutex, INFINITE) ; 
读者可以运行这时的MultiThread程序,将会发现线程1和线程2没有执行if语句下的代码。我们分析
这时的程序调用情况,当调用WaitForSingleObject函数请求互斥对象时,操作系统需要判断当前请
求互斥对象的线程的ID是否与互斥对象当前拥有者的线程ID相等,如果相等,即使该互斥对象处于未
通知状态,调用线程仍然能够获得其所有权,然后 WaitForSingleObject函数返回。对于同一个线
程多次拥有的互斥对象来说,该互斥对象内部的计数器记录了该线程拥有的次数。在本例中,当第
一次创建互斥对象时,主线程拥有这个互斥对象,除了将互斥对象的线程ID设置为主线程的ID以外,
同时还将该互斥对象内部的计数器变为 1。这里应注意,当主线程拥有该互斥对象时,该对象就处
于未通知状态了,但是当在主线程中调用WaitForSingleObject函数请求该互斥对象的所有权时,因
为请求的线程的ID和该互斥对象当前所有者的线程ID是相同的,所以仍然能够请求到这个互斥对象,
操作系统通过互斥对象内部的计数器来维护同一个线程请求到该互斥对象的次数,于是该计数器就
会增加1,这时,互斥对象内部计数器的值为 2。当接下来调用 ReleaseMutex函数释放该互斥对象
的所有权时,实际上就是递减这个计数器,但此时该计数器的值仍为1,因此操作系统不会将这个互
斥对象变为己通知状态。当然,随后线程 1和线程2请求这个互斥对象时,它们是得不到该对象的所
有权的。
如果想让线程 1和线程 2能够执行 if语句下的代码,只有在主线程中再次调用 ReleaseMutex函数,
这时该互斥对象内部维护的计数器就变成0了,操作系统就会将该互斥对象的线程ID设置为0,同时
将该对象设置为有信号状态。之后,线程 1和线程2就可以请求到该互斥对象的所有权了。读者可以
自行测试这种情况。
正是因为互斥对象具有与线程相关这一特点,所以在使用互斥对象时需要小心仔细,
如果多次在同一个线程中请求同一个互斥对象,那么就需要相应地多次调用ReleaseMutex函数释放
该互斥对象。下面我们再看一种情况,我们将两个线程的入口函数改为如例 15-6所示代码: 
例 15-6 

//线程 1的入口函数 
DWORD WINAPI Fun1Proc( LPVOID lpParameter 11 thread data 
WaitForSingleObject(hMutex,INFINITE) ; 
cout<< "thread1 is running" <<endl; 
return 0 ; 
11线程2的入口函数 
DWORD WINAPI Fun2Proc( LPVOID lpparameter 11 thread data 
WaitForSingleObject(hMutex, INFINITE) ; 
cout<<"thread2 is running"<<endl; 
return 0 ; 
在如例 15-6所示代码中,线程l请求互斥对象之后输出一句话: thread1 is running, 接下来它并
没有释放该互斥对象就退出了。线程2的函数实现代码是一样的。那么现在线程2能获得互斥对象的
所有权吗?我们可以运行这时的MultiThread程序,结果如图 15.11所示,可以看到,线程l和线程2
都完整地运行了。

图 15.11利用互斥对象实现多线程同步示例程序结果

在程序运行时,操作系统维护了线程的信息以及与该线程相关的互斥对象的信息,因此它知道哪个
线程终止了。如果某个线程得到其所需互斥对象的所有权,完成其钱程代码的运行,但没有释放该
互斥对象的所有权就退出之后,操作系统一旦发现该线程已经终止,它就会自动将该线程所拥有的
互斥对象的线程ID设为0,并将其计数器归0。因此,在本例中,一旦操作系统判断出线程 1终止了,
那么它就会将互斥对象的引用计数置为0,线程ID也置为0,这时线程2就可以得到互斥对象的所有权。 
另外,可以根据 WaitForSingleObject函数的返回值知道当前线程是如何得到互斥对象的所有权的,
是正常得到的,还是因先前拥有该对象的线程退出后获得的。但是如果判断其返回值为 
WAIT_ABANDONED时,那就要小心了,由于不知道是因为先前拥有该对象的线程在终止之前没有调用 
ReleaseMutex函数释放所有权,还是先前拥有该对象的线程异常终止,这时在线程中,通过 
WaitForSingleObject函数所保护的代码,它们所访问的资源当前处于什么状态是不清楚的,如果这
时进入这段代码对所保护的资源进行操作,结果将是未知的。因此,在程序中应该根据 
WaitForSingleObject函数的返回值进行一些相应处理。


⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -