您的位置:首页 > 编程语言 > C语言/C++

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

2014-04-18 08:56 645 查看

一、概念

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

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

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

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

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

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

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

3.pagefile.sys 页文件

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

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

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

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

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

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

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

 

二、创建多线程

[cpp] view
plaincopy

1.创建线程的函数  

  

(1)HANDLE CreateThread(  

  

LPSECURITY_ATTRIBUTES lpThreadAttributes,   

  

DWORD dwStackSize,  

  

LPTHREAD_START_ROUTINE lpStartAddress,  

  

LPVOID lpParameter,  

  

DWORD dwCreationFlags,  

  

LPDWORD lpThreadId );    

  

参数1:指向SECURITY_ATTRIBUTES结构体的指针。这里可以设置为NULL,使用缺省的安全性。  

参数2:指定初始提交的栈的大小,以字节为单位。系统会将这个值四舍五入为最近的页面。  

 (页面:是系统管理内存时使用的内存单位,不同的CPU其页面大小也是不同的。X86  

 使用的页面大小是4KB。当保留地址空间的一个区域时,系统要确保该区域的大小是  

 系统的页面大小的倍数)  

 如果该值是0或者小于缺省提交大小,则使用和调用线程一样的大小。  

参数3:指向LPTHREAD_START_ROUTINE(应用程序定义的函数类型)的指针。这个函数将被线程  

 执行,表示了线程的起始地址。看线程入口函数ThreadProc。  

参数4:指定传递给线程的单独的参数的值。  

参数5:指定控制线程创建的附加标记。如果CREATE_SUSPENDED标记被指定,线程创建后处于暂停  

 状态不会运行,直到调用了ResumeThread函数。  

 如果该值是0,线程在创建之后立即运行。  

参数6:[out]指向一个变量用来接收线程的标识符。创建一个线程时,系统会为线程分配一个ID号。  

 Windows NT/2000:如果这个参数是NULL,线程的标识符不会返回。  

 Windows 95/98  :这个参数不能是NULL    

  

如果线程创建成功,此函数返回线程的句柄。  

  

   

  

2.线程入口函数的形式  

  

DWORD WINAPI ThreadProc(LPVOID lpParameter);//ThreadProc为函数名,可以更改为任何函数名  

  

   

  

3.void Sleep(  DWORD dwMilliseconds); //此函数的参数单位为毫秒  

  

暂停当前线程指定时间间隔的执行。  

  

当线程暂停执行的时候,也就是表示它放弃了执行的权力。  

操作系统会从等待运行的线程队列中选择一个线程来运行。新创建的线程就可以得到运行的机会。  

  

   

  

4.代码如下:  

  

#include "stdafx.h"  

#include <Windows.h>  

#include <iostream>  

  

using namespace std;  

  

DWORD WINAPI Thread1Proc(LPVOID lpParameter);  

  

  

void main()  

{  

 HANDLE hThread1;  

 DWORD dwThread1ID;  

 hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,&dwThread1ID);  

 cout<<"main Thread is running"<<endl;  

 Sleep(4000);  

 //return;  

}  

  

DWORD WINAPI Thread1Proc(LPVOID lpParameter)  

{  

 cout<<"Thread1 is running"<<endl;  

 return 0;  

}  

 

三、命名互斥

[cpp] view
plaincopy

(1)创建互斥对象  

  

HANDLE CreateMutex(  

 LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全性  

 BOOL bInitialOwner,  // flag for initial ownership,  

 LPCTSTR lpName     // pointer to mutex-object name  

 );  

  

打开一个命名的或者没有名字的互斥对象:  

参数1:指向SECURITY_ATTRIBUTES结构体的指针。可以传递NULL,让其使用默认的安全性。  

参数2:指示互斥对象的初始拥有者。  

 如果该值是真,调用者创建互斥对象,调用的线程获得互斥对象的所有权。  

 否则,调用线程捕获互斥对象的所有权。(就是说,如果该参数为真,则调用  

 该函数的线程拥有互斥对象的所有权。否则,不拥有所有权)  

参数3:互斥对象名称。传递NULL创建的就是没有名字的互斥对象,即匿名的互斥对象。  

  

创建匿名互斥对象时,当前没有线程拥有互斥对象,操作系统会将互斥对象设置为已通知状态(有信号状态)  

  

如果一个命名的互斥对象在本函数调用之前已经存在,则返回已经存在的对象句柄。  

然后可以调用GetLastError检查其返回值是否为ERROR_ALREADY_EXISTS。  

  

(2)在线程中请求互斥对象  

  

DWORD WaitForSingleObject(  

  

HANDLE     hHandle,  

  

DWORD    dwMilliseconds  

  

);  

  

 参数1:对象的句柄,这里传递的是互斥对象的句柄。  

  一旦互斥对象变成有信号状态,该函数返回。  

  如果互斥对象始终没有处于有信号状态(非信号状态),  

  函数将一直处于等待,从而导致线程暂停运行。  

 参数2:指定超时的时间间隔,以毫秒为单位。  

  如果时间间隔流逝了,函数就返回,即使等待的互斥对象处于非信号状态;  

  如果将该参数设置为0,该函数测试互斥对象的状态后立即返回;  

  如果将该参数设置为INFINITE,函数的超时值永远不会发生,  

  也就是说函数将永远等待,直到所等待的对象处于有信号状态。  

  

  

注意:  

  可以在我们需要保护的代码前面加上WaitForSingleObject(),  

  当我们请求互斥对象的时候操作系统会判断请求互斥对象的线程  

  和拥有互斥对象的线程的ID是否相等,如果相等,即使互斥对象处于未通知状态  

  (非信号状态),仍然能够获得互斥对象的所有权。  

  操作系统通过互斥对象的计数器记录请求了多少次互斥对象。  

  

   

  

(3)释放互斥对象  

  

在所要保护的代码操作完成之后,要用ReleaseMutex方法释放互斥对象。  

 BOOL ReleaseMutex(  

 HANDLE hMutex   // handle to mutex object  

 );  

  //本函数如果成功返回非0值,失败返回0。  

  

  

 调用本函数会将互斥对象的ID设置为0,并使互斥对象处于  

 已通知状态(有信号状态),同时将互斥对象的计数器减一。  

 本函数只能被拥有互斥对象所有权的线程调用,其他线程无法释放互斥对象。  

 因为互斥对象会保存所有者的线程ID,在调用ReleaseMutex时会先判断一下这个线程与  

 互斥对象保存的ID是否一致,如果不是一致则不能成功释放。  

 释放互斥对象的原则:谁拥有,谁释放。  

  

  

4.调用的形式  

 //在主线程中  

 ...  

 HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);  

 ...  

 //其他线程中  

 ...  

 WaitForSingleObject(hMutex, INFINITE);  

 //受保护的代码  

 ...  

 ReleaseMutex(hMutex);  

  

--------------------------------------------------------------------------------  

 1.互斥对象包含一个计数器,用来记录互斥对象请求的次数,  

 所以在同一线程中请求了多少次就要释放多少次;  

 如 hMutex=CreateMutex(NULL,TRUE,NULL);  

  //当第二个参数设置为TRUE时,互斥对象计数器设为1  

 WaitForSingleObject(hMutex,INFINITE);  

  //因为请求的互斥对象线程ID与拥有互斥对象线程ID相同,可以再次请求成功,计数器加1  

 ReleaseMutex(hMutex);  //第一次释放,计数器减1,但仍有信号  

 ReleaseMutex(hMutex);  //再一次释放,计数器为零  

  

   

  

2.如果操作系统发现线程已经正常终止,会自动把线程申请的互斥对象ID设为0,  

   同时也把计数器清零,其他对象可以申请互斥对象。  

  

   

  

3.可以根据WaitForSingleObject的返回值判断该线程是如何得到互斥对象拥有权的  

  如果返回值是WAIT_OBJECT_0,表示由于互斥对象处于有信号状态才获得所有权的  

  如果返回值是WAIT_ABANDONED,则表示先前拥有互斥对象的线程异常终止  

  或者终止之前没有调用 ReleaseMutex释放对象,此时就要警惕了,访问资源有破坏资源的危险  

 

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

[cpp] view
plaincopy

#include "stdafx.h"  

#include <Windows.h>  

#include <iostream>  

  

using namespace std;  

  

DWORD WINAPI ThreadProc1(LPVOID lpParameter);  

DWORD WINAPI ThreadProc2(LPVOID lpParameter);  

  

int nIndex = 0;  

HANDLE hMutex;  

int nTicket = 10;  

  

void main()  

{  

 HANDLE handle1,handle2;  

 handle1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);  

 handle2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);  

 CloseHandle(handle1);  

 CloseHandle(handle2);  

  

 hMutex = CreateMutex(NULL,TRUE,_T("tickets"));  

 if (hMutex)  

 {  

  if(ERROR_ALREADY_EXISTS == GetLastError())  

  {  

   cout<<"已有一个实例正在运行"<<endl;  

   return;  

  }  

 }  

 WaitForSingleObject(hMutex,INFINITE);  

 ReleaseMutex(hMutex);  

 ReleaseMutex(hMutex);  

  

 Sleep(4000);  

 system("pause");  

}  

  

   

  

DWORD WINAPI ThreadProc1(LPVOID lpParameter)  

{  

 /*WaitForSingleObject(hMutex,INFINITE);*/  

 while (TRUE)  

 {  

  WaitForSingleObject(hMutex,INFINITE);  

  if (nTicket>0)  

  {  

   cout<<"thread1 is running "<<nTicket--<<endl;  

  }  

  else  

  {  

   break;  

  }  

  ReleaseMutex(hMutex);  

 }  

   

 return 0;  

}  

  

   

  

DWORD WINAPI ThreadProc2(LPVOID lpParameter)  

{  

   

 while (TRUE)  

 {  

  WaitForSingleObject(hMutex,INFINITE);  

  if (nTicket>0)  

  {  

   cout<<"thread2 is running "<<nTicket--<<endl;  

  }  

  else  

  {  

   break;  

  }  

  

  ReleaseMutex(hMutex);  

 }  

 return 0;  

}  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: