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

一、概念

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

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

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

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

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

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

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

3.pagefile.sys 页文件

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

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

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

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

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

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

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

 

二、创建多线程

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;
}


 

三、命名互斥

(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释放对象,此时就要警惕了,访问资源有破坏资源的危险

 

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

#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;
}


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值