孙鑫vc++ 15 (1)多线程与命名互斥

时间:2022-06-10 09:44:11

一、概念

1.进程(由两部分组成):

内核对象:操作系统通过访问内核对象,对进程进行管理

地址空间:包含代码,数据,动态分配的内存空间,比如堆、栈分配的空间

进程不执行任何东西,它只是作为线程的容器,由线程完成代码的执行

2.主线程(由进程创建)

主线程的入口:main或者WinMain函数

进程中的其他线程都是由主线程创建

3.pagefile.sys 页文件

虚拟内存:在磁盘中划分出一块当内存使,划分出来的那一块就是pagefile.sys

4.线程(由两个部分组成):

内核对象:操作系统通过访问内核对象,对线程进行管理,可以将其看做一个小型的数据结构

线程堆栈:维护线程的所有参数和局部变量

5.尽量使用多线程解决问题而不使用多进程的原因:

(1)进程占用的内存空间和资源都不叫多

(2)进程之间相互切换需要交换页空间和内存地址、资源等;但是线程之间相互交换只需要变一个执行环境,所用的资源都还是进程的共享资源

 

二、创建多线程

[cpp] view plaincopy
  1. 1.创建线程的函数  
  2.   
  3. (1)HANDLE CreateThread(  
  4.   
  5. LPSECURITY_ATTRIBUTES lpThreadAttributes,   
  6.   
  7. DWORD dwStackSize,  
  8.   
  9. LPTHREAD_START_ROUTINE lpStartAddress,  
  10.   
  11. LPVOID lpParameter,  
  12.   
  13. DWORD dwCreationFlags,  
  14.   
  15. LPDWORD lpThreadId );    
  16.   
  17. 参数1:指向SECURITY_ATTRIBUTES结构体的指针。这里可以设置为NULL,使用缺省的安全性。  
  18. 参数2:指定初始提交的栈的大小,以字节为单位。系统会将这个值四舍五入为最近的页面。  
  19.  (页面:是系统管理内存时使用的内存单位,不同的CPU其页面大小也是不同的。X86  
  20.  使用的页面大小是4KB。当保留地址空间的一个区域时,系统要确保该区域的大小是  
  21.  系统的页面大小的倍数)  
  22.  如果该值是0或者小于缺省提交大小,则使用和调用线程一样的大小。  
  23. 参数3:指向LPTHREAD_START_ROUTINE(应用程序定义的函数类型)的指针。这个函数将被线程  
  24.  执行,表示了线程的起始地址。看线程入口函数ThreadProc。  
  25. 参数4:指定传递给线程的单独的参数的值。  
  26. 参数5:指定控制线程创建的附加标记。如果CREATE_SUSPENDED标记被指定,线程创建后处于暂停  
  27.  状态不会运行,直到调用了ResumeThread函数。  
  28.  如果该值是0,线程在创建之后立即运行。  
  29. 参数6:[out]指向一个变量用来接收线程的标识符。创建一个线程时,系统会为线程分配一个ID号。  
  30.  Windows NT/2000:如果这个参数是NULL,线程的标识符不会返回。  
  31.  Windows 95/98  :这个参数不能是NULL    
  32.   
  33. 如果线程创建成功,此函数返回线程的句柄。  
  34.   
  35.    
  36.   
  37. 2.线程入口函数的形式  
  38.   
  39. DWORD WINAPI ThreadProc(LPVOID lpParameter);//ThreadProc为函数名,可以更改为任何函数名  
  40.   
  41.    
  42.   
  43. 3.void Sleep(  DWORD dwMilliseconds); //此函数的参数单位为毫秒  
  44.   
  45. 暂停当前线程指定时间间隔的执行。  
  46.   
  47. 当线程暂停执行的时候,也就是表示它放弃了执行的权力。  
  48. 操作系统会从等待运行的线程队列中选择一个线程来运行。新创建的线程就可以得到运行的机会。  
  49.   
  50.    
  51.   
  52. 4.代码如下:  
  53.   
  54. #include "stdafx.h"  
  55. #include <Windows.h>  
  56. #include <iostream>  
  57.   
  58. using namespace std;  
  59.   
  60. DWORD WINAPI Thread1Proc(LPVOID lpParameter);  
  61.   
  62.   
  63. void main()  
  64. {  
  65.  HANDLE hThread1;  
  66.  DWORD dwThread1ID;  
  67.  hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,&dwThread1ID);  
  68.  cout<<"main Thread is running"<<endl;  
  69.  Sleep(4000);  
  70.  //return;  
  71. }  
  72.   
  73. DWORD WINAPI Thread1Proc(LPVOID lpParameter)  
  74. {  
  75.  cout<<"Thread1 is running"<<endl;  
  76.  return 0;  
  77. }  


 

三、命名互斥

[cpp] view plaincopy
  1. (1)创建互斥对象  
  2.   
  3. HANDLE CreateMutex(  
  4.  LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全性  
  5.  BOOL bInitialOwner,  // flag for initial ownership,  
  6.  LPCTSTR lpName     // pointer to mutex-object name  
  7.  );  
  8.   
  9. 打开一个命名的或者没有名字的互斥对象:  
  10. 参数1:指向SECURITY_ATTRIBUTES结构体的指针。可以传递NULL,让其使用默认的安全性。  
  11. 参数2:指示互斥对象的初始拥有者。  
  12.  如果该值是真,调用者创建互斥对象,调用的线程获得互斥对象的所有权。  
  13.  否则,调用线程捕获互斥对象的所有权。(就是说,如果该参数为真,则调用  
  14.  该函数的线程拥有互斥对象的所有权。否则,不拥有所有权)  
  15. 参数3:互斥对象名称。传递NULL创建的就是没有名字的互斥对象,即匿名的互斥对象。  
  16.   
  17. 创建匿名互斥对象时,当前没有线程拥有互斥对象,操作系统会将互斥对象设置为已通知状态(有信号状态)  
  18.   
  19. 如果一个命名的互斥对象在本函数调用之前已经存在,则返回已经存在的对象句柄。  
  20. 然后可以调用GetLastError检查其返回值是否为ERROR_ALREADY_EXISTS。  
  21.   
  22. (2)在线程中请求互斥对象  
  23.   
  24. DWORD WaitForSingleObject(  
  25.   
  26. HANDLE     hHandle,  
  27.   
  28. DWORD    dwMilliseconds  
  29.   
  30. );  
  31.   
  32.  参数1:对象的句柄,这里传递的是互斥对象的句柄。  
  33.   一旦互斥对象变成有信号状态,该函数返回。  
  34.   如果互斥对象始终没有处于有信号状态(非信号状态),  
  35.   函数将一直处于等待,从而导致线程暂停运行。  
  36.  参数2:指定超时的时间间隔,以毫秒为单位。  
  37.   如果时间间隔流逝了,函数就返回,即使等待的互斥对象处于非信号状态;  
  38.   如果将该参数设置为0,该函数测试互斥对象的状态后立即返回;  
  39.   如果将该参数设置为INFINITE,函数的超时值永远不会发生,  
  40.   也就是说函数将永远等待,直到所等待的对象处于有信号状态。  
  41.   
  42.   
  43. 注意:  
  44.   可以在我们需要保护的代码前面加上WaitForSingleObject(),  
  45.   当我们请求互斥对象的时候操作系统会判断请求互斥对象的线程  
  46.   和拥有互斥对象的线程的ID是否相等,如果相等,即使互斥对象处于未通知状态  
  47.   (非信号状态),仍然能够获得互斥对象的所有权。  
  48.   操作系统通过互斥对象的计数器记录请求了多少次互斥对象。  
  49.   
  50.    
  51.   
  52. (3)释放互斥对象  
  53.   
  54. 在所要保护的代码操作完成之后,要用ReleaseMutex方法释放互斥对象。  
  55.  BOOL ReleaseMutex(  
  56.  HANDLE hMutex   // handle to mutex object  
  57.  );  
  58.   //本函数如果成功返回非0值,失败返回0。  
  59.   
  60.   
  61.  调用本函数会将互斥对象的ID设置为0,并使互斥对象处于  
  62.  已通知状态(有信号状态),同时将互斥对象的计数器减一。  
  63.  本函数只能被拥有互斥对象所有权的线程调用,其他线程无法释放互斥对象。  
  64.  因为互斥对象会保存所有者的线程ID,在调用ReleaseMutex时会先判断一下这个线程与  
  65.  互斥对象保存的ID是否一致,如果不是一致则不能成功释放。  
  66.  释放互斥对象的原则:谁拥有,谁释放。  
  67.   
  68.   
  69. 4.调用的形式  
  70.  //在主线程中  
  71.  ...  
  72.  HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);  
  73.  ...  
  74.  //其他线程中  
  75.  ...  
  76.  WaitForSingleObject(hMutex, INFINITE);  
  77.  //受保护的代码  
  78.  ...  
  79.  ReleaseMutex(hMutex);  
  80.   
  81. --------------------------------------------------------------------------------  
  82.  1.互斥对象包含一个计数器,用来记录互斥对象请求的次数,  
  83.  所以在同一线程中请求了多少次就要释放多少次;  
  84.  如 hMutex=CreateMutex(NULL,TRUE,NULL);  
  85.   //当第二个参数设置为TRUE时,互斥对象计数器设为1  
  86.  WaitForSingleObject(hMutex,INFINITE);  
  87.   //因为请求的互斥对象线程ID与拥有互斥对象线程ID相同,可以再次请求成功,计数器加1  
  88.  ReleaseMutex(hMutex);  //第一次释放,计数器减1,但仍有信号  
  89.  ReleaseMutex(hMutex);  //再一次释放,计数器为零  
  90.   
  91.    
  92.   
  93. 2.如果操作系统发现线程已经正常终止,会自动把线程申请的互斥对象ID设为0,  
  94.    同时也把计数器清零,其他对象可以申请互斥对象。  
  95.   
  96.    
  97.   
  98. 3.可以根据WaitForSingleObject的返回值判断该线程是如何得到互斥对象拥有权的  
  99.   如果返回值是WAIT_OBJECT_0,表示由于互斥对象处于有信号状态才获得所有权的  
  100.   如果返回值是WAIT_ABANDONED,则表示先前拥有互斥对象的线程异常终止  
  101.   或者终止之前没有调用 ReleaseMutex释放对象,此时就要警惕了,访问资源有破坏资源的危险  

 

四、通过命名互斥来保证应用程序只能同时运行一个实例

[cpp] view plaincopy
  1. #include "stdafx.h"  
  2. #include <Windows.h>  
  3. #include <iostream>  
  4.   
  5. using namespace std;  
  6.   
  7. DWORD WINAPI ThreadProc1(LPVOID lpParameter);  
  8. DWORD WINAPI ThreadProc2(LPVOID lpParameter);  
  9.   
  10. int nIndex = 0;  
  11. HANDLE hMutex;  
  12. int nTicket = 10;  
  13.   
  14. void main()  
  15. {  
  16.  HANDLE handle1,handle2;  
  17.  handle1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);  
  18.  handle2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);  
  19.  CloseHandle(handle1);  
  20.  CloseHandle(handle2);  
  21.   
  22.  hMutex = CreateMutex(NULL,TRUE,_T("tickets"));  
  23.  if (hMutex)  
  24.  {  
  25.   if(ERROR_ALREADY_EXISTS == GetLastError())  
  26.   {  
  27.    cout<<"已有一个实例正在运行"<<endl;  
  28.    return;  
  29.   }  
  30.  }  
  31.  WaitForSingleObject(hMutex,INFINITE);  
  32.  ReleaseMutex(hMutex);  
  33.  ReleaseMutex(hMutex);  
  34.   
  35.  Sleep(4000);  
  36.  system("pause");  
  37. }  
  38.   
  39.    
  40.   
  41. DWORD WINAPI ThreadProc1(LPVOID lpParameter)  
  42. {  
  43.  /*WaitForSingleObject(hMutex,INFINITE);*/  
  44.  while (TRUE)  
  45.  {  
  46.   WaitForSingleObject(hMutex,INFINITE);  
  47.   if (nTicket>0)  
  48.   {  
  49.    cout<<"thread1 is running "<<nTicket--<<endl;  
  50.   }  
  51.   else  
  52.   {  
  53.    break;  
  54.   }  
  55.   ReleaseMutex(hMutex);  
  56.  }  
  57.    
  58.  return 0;  
  59. }  
  60.   
  61.    
  62.   
  63. DWORD WINAPI ThreadProc2(LPVOID lpParameter)  
  64. {  
  65.    
  66.  while (TRUE)  
  67.  {  
  68.   WaitForSingleObject(hMutex,INFINITE);  
  69.   if (nTicket>0)  
  70.   {  
  71.    cout<<"thread2 is running "<<nTicket--<<endl;  
  72.   }  
  73.   else  
  74.   {  
  75.    break;  
  76.   }  
  77.   
  78.   ReleaseMutex(hMutex);  
  79.  }  
  80.  return 0;  
  81. }