一、概念
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;
- }