多线程之互斥对象(续)

上一节中说到多线程的用处,但没有举到例子。这次是一个模拟售票系统的多线程程序,主线程负责创建2个子线程,而子线程负责将票售完。全局变量tickets是共享资源,每一个线程均可访问它。

 

代码1:

  
  
  
  
  1. #include <windows.h>  
  2. #include <iostream.h>  
  3.  
  4. int tickets=100;  
  5.  
  6. //线程入口函数的声明  
  7. DWORD WINAPI Func1Proc(LPVOID lpParameter);  
  8. DWORD WINAPI Func2Proc(LPVOID lpParameter);  
  9.  
  10. //主线程  
  11. int main()  
  12. {  
  13.     HANDLE hThread1;  
  14.     HANDLE hThread2;  
  15.  
  16.     hThread1=CreateThread(NULL,0,Func1Proc,NULL,0,NULL);  
  17.     hThread2=CreateThread(NULL,0,Func2Proc,NULL,0,NULL);  
  18.     CloseHandle(hThread1);  
  19.     CloseHandle(hThread2);  
  20.       
  21.     Sleep(4000);  
  22.       
  23.     return 0;  
  24. }  
  25.  
  26. //线程1的入口函数  
  27. DWORD WINAPI Func1Proc(LPVOID lpParameter)  
  28. {  
  29.     while(true)  
  30.     {  
  31.  
  32.         if(tickets>0)  
  33.         {  
  34.            Sleep(1);  
  35.            cout<<"thread1 sell tickets: "<<tickets--<<endl;  //tickets先输出,再减1  
  36.         }  
  37.         else 
  38.         {  
  39.            break;  
  40.         }  
  41.     }  
  42.       
  43.     return 0;  
  44. }  
  45. //线程2的入口函数  
  46. DWORD WINAPI Func2Proc(LPVOID lpParameter)  
  47. {  
  48.     while(true)  
  49.     {  
  50.         if(tickets>0)  
  51.         {  
  52.            Sleep(1);  
  53.            cout<<"thread2 sell tickets: "<<tickets--<<endl;  //tickets先输出,再减1  
  54.         }  
  55.         else 
  56.         {  
  57.            break;  
  58.         }  
  59.     }  
  60.       
  61.     return 0;  
  62. }  

但程序的运行结果是,杂乱无章,为什么呢?因为对线程而言,时间片转到它,它就运行,比如线程1刚好执行完if那一行代码(假设此时tickets=1),时间片却轮到了线程2,在售出为1的票号之后(再假如这时tickets尚没有减1,时间片又到了,此时tickets仍然等于1),接着再次轮到线程1,继续执行刚才的代码,也售出为1的票号;或者(假如这时tickets已经减1,时间片又到了,此时tickets等于0),接着再次轮到线程1,执行之后,售出的是为0的票号。这两种情况都是很糟糕的,尤其是发生在现实生活中的时候,既不能售出票号一样的票,更不能售出不存在的票号。在上面的程序中,还加了两条Sleep函数语句,但无论加不加,结果同样都坏。

证据:

(可以看到,不仅如此,如果有这种情况出现,事实可能更为糟糕!)

 

--------------------------------------------------------------------------------------------------直到这个时候,多线程之互斥对象的Mutex才浮出水面。互斥对象真正要实现的作用是,使共享资源tickets得到协调使用(即所谓的线程同步,如果这样命名的话,其实并不好理解)。它的实现原理是,通过调用函数WaitForSingleObject来获得互斥对象的拥有权,一旦该线程获得互斥对象,那么别的线程必须无限期等待(既然处于等待状态,它的代码也没有办法执行),直到拥有互斥对象的线程调用函数ReleaseMutex释放互斥对象的拥有权。说到这里,互斥对象的机制应该就比较清楚了。

代码2:

  
  
  
  
  1. #include <windows.h>  
  2. #include <iostream.h>  
  3.  
  4. int tickets=100; //共享资源  
  5. HANDLE hMutex;   //共享对象(因为是全局变量嘛~)  
  6.  
  7. //线程入口函数的声明  
  8. DWORD WINAPI Func1Proc(LPVOID lpParameter);  
  9. DWORD WINAPI Func2Proc(LPVOID lpParameter);  
  10.  
  11. //主线程  
  12. int main()  
  13. {  
  14.     HANDLE hThread1;  
  15.     HANDLE hThread2;  
  16.  
  17.     /*安全属性(SecurityAttributes),  
  18.      *是否拥有互斥对象(InitialOwner),  
  19.      *是否是匿名的互斥对象(MutexName)  
  20.      */ 
  21.     hMutex=CreateMutex(NULL,FALSE,NULL);   
  22.       
  23.     hThread1=CreateThread(NULL,0,Func1Proc,NULL,0,NULL);  
  24.     hThread2=CreateThread(NULL,0,Func2Proc,NULL,0,NULL);  
  25.     CloseHandle(hThread1);  
  26.     CloseHandle(hThread2);  
  27.       
  28.     Sleep(4000);  
  29.       
  30.     return 0;  
  31. }  
  32.  
  33. //线程1的入口函数  
  34. DWORD WINAPI Func1Proc(LPVOID lpParameter)  
  35. {  
  36.     while(true)  
  37.     {  
  38.         WaitForSingleObject(hMutex,INFINITE);  
  39.         if(tickets>0)  
  40.         {  
  41.            
  42.            cout<<"thread1 sell tickets: "<<tickets--<<endl;  //tickets先输出,再减1  
  43.         }  
  44.         else 
  45.         {  
  46.            break;  
  47.         }  
  48.         ReleaseMutex(hMutex);  
  49.     }  
  50.       
  51.     return 0;  
  52. }  
  53. //线程2的入口函数  
  54. DWORD WINAPI Func2Proc(LPVOID lpParameter)  
  55. {  
  56.     while(true)  
  57.     {  
  58.         WaitForSingleObject(hMutex,INFINITE);  
  59.         if(tickets>0)  
  60.         {  
  61.              
  62.            cout<<"thread2 sell tickets: "<<tickets--<<endl;  //tickets先输出,再减1  
  63.         }  
  64.         else 
  65.         {  
  66.            break;  
  67.         }  
  68.         ReleaseMutex(hMutex);  
  69.     }  
  70.       
  71.     return 0;  
  72. }  

这个程序需要注意的是,函数CreateMutex的3个参数和函数WaitForSingleObject的返回值,解释如下:

WINBASEAPI
HANDLE
WINAPI
CreateMutexA(
    LPSECURITY_ATTRIBUTES lpMutexAttributes, //一般为NULL,表示默认的安全属性
    BOOL bInitialOwner,                                         //当为FALSE时,表示创建互斥对象的主线程没                                                                        有互斥对象的拥有权                            

  LPCSTR lpName                                                     //当为NULL时,表示匿名的互斥对象
    );

WaitForSingleObject函数的返回值,①WAIT_OBJECT_0,所请求的对象是有信号状态;②WAIT_TIMEOUT,指定的时间间隔已过,并且所请求的对象是无信号状态;③WAIT_ABANDONED,所请求的对象是一个互斥对象,并且先前拥有该对象的线程在终止前没有释    放该对象,该对象的拥有权将授予当前调用线程,并且将该互斥对象设为无信号状态。

(显然,这时候不存在任何的问题。)

 

继续做实验,加深理解Mutex(实验结果略):

①如果去掉线程1循环里的ReleaseMutex那一行代码,那么线程2将无法得到互斥对象,永远等待下去,运行结果只有线程1在售票;

②如果主线程里的函数CreateMutex的第二个参数为TRUE(表示主线程此时有互斥对象的拥有权),那么线程1和线程2都没有办法得到互斥对象,只有当主线程里成对的出现函数ReleaseMutex时,也就是主线程释放互斥对象的拥有权,线程1和线程2才能够再次拥有互斥对象。

③如果主线程,在函数CreateMutex的第二个参数为TRUE的基础上(此时,互斥对象为无信号状态),再添加函数WaitForSingleObject,由于申请互斥对象的主线程(它的线程ID)和互斥对象的拥有者(即主线程的ID)相同,所以即使互斥对象为无信号也可以再次申请到互斥对象。现在,互斥对象的内部记录是,计数器(哪个线程拥有它的次数)为2,如果要释放互斥对象的话,需要调用函数WaitForSingleObject两次。

你可能感兴趣的:(多线程,程序,休闲,共享资源,票)