进程由两个部分组成:
1、操作系统用来管理进程的内核对象。
内核对象:
是操作系统内部分配的一个内存块,该内存块也是一种数据结构,我们不能够直接访问,应用程序无法访问,只能使用windows提供的函数操作该内存块。
2、地址空间
包含所有可执行模块或dll模块的代码和数据,还包括动态内存分配的空间。
进程:
不活泼,只是线程的容器,是线程的执行环境。
第一个线程 – 主线程。
线程占用的资源比进程小。
创建线程:
#include <Windows.h>
#include <iostream>
using namespace std;
int index =0;
DWORD WINAPI Fun1Proc( LPVOID lpParameter)
{
while(index++ <1000)
{
cout<<"thread1 thread is running \n";
}
return 0;
}
void main1()
{
HANDLE hThread1;
hThread1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
CloseHandle(hThread1);//关闭句柄,让线程对象的引用计数为0.但是线程仍可以运行。这里只是在主函数使用不到线程句柄了,所以将其关闭。
while(index++<1000)
{
cout<<"main thread is running \n";
}
system("pause");
}
线程同步方案
互斥对象:
1、内核对象。它能够确保拥有对单个资源的互斥访问权。
2、互斥对象包含一个使用数量,一个线程ID,一个计数器。
ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
使用步骤:
1、创建互斥对象:CreateMutex
2、请求互斥对象:WaitForSingleObject
3、释放互斥对象:ReleaseMutex
#include <Windows.h>
#include <iostream>
using namespace std;
int tickets = 100;
HANDLE hMutex;
DWORD WINAPI Func1 (LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if(tickets > 0)//bug,判断成功,进入if语句块,但是,此时线程的执行权被剥夺。so。可能出现卖出0号票的可能。解决方案:线程锁。线程同步。
{
Sleep(1);
cout<<"thread1 sell ticket :" <<tickets-- <<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
DWORD WINAPI Func2 (LPVOID lpParameter)
{
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
if(tickets > 0)
{
Sleep(1);
cout<<"thread2 sell ticket :" <<tickets-- <<endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
void main()
{
HANDLE hThread,hThread2;
hThread = CreateThread(NULL,0,Func1,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Func2,NULL,0,NULL);
CloseHandle(hThread);
CloseHandle(hThread2);
//创建一个匿名的互斥对象
//如果第二个参数设置为TRUE的话,表示当前创建互斥对象的线程(主线程)拥有了互斥对象,而其没有释放,所以,其他的线程请求互斥对象的时候只能处于等待状态。
//hMutex =CreateMutex(NULL,FALSE,NULL);
hMutex =CreateMutex(NULL,TRUE,NULL);
WaitForSingleObject(hMutex,INFINITE);
ReleaseMutex(hMutex);
ReleaseMutex(hMutex);
Sleep(4000);
system("pause");
}
代码解析:
1、首先创建一个匿名的互斥对象:hMutex =CreateMutex(NULL,FALSE,NULL);第二个参数表示该对象的拥有者,为FALSE则没有拥有者,所以该互斥对象处于有信号状态。有信号状态表明该互斥对象可用,哪个线程拿到它就可以先执行,一直到释放该互斥对象,其他的线程才有执行权。
2、当thread1开始进行循环售票时,先绑定互斥对象,此时的互斥对象内部的ID就被设置为当前所在线程的ID,且互斥对象处于无限号状态。尽管此时thread1中的代码在Sleep(1);thread2尽管拿到了线程执行权,也不能够再绑定该匿名互斥对象,因为该互斥对象现在处于无信号状态,thread2只能等待。
3、当thread1将互斥对象释放之后,thread2才可以绑定该互斥对象,只有当thread2绑定了互斥对象,thread2才可以执行它自己的代码。
一般互斥对象就是为了解决不同的线程对共有资源的并发访问而导致的问题。
互斥对象是和线程相关的,只能使用拥有互斥对象的线程释放相应的互斥对象,其他的线程是不能够释放的。即谁拥有互斥对象,只有谁能释放互斥对象。
当一个线程请求互斥对象时,如果互斥对象处于有信号状态,即没有其他的线程拥有该互斥对象,则当前线程请求互斥对象成功,系统就会将当前线程ID设置为互斥对象中的线程ID。线程ID是唯一标识一个线程的,当其他线程想要释放该互斥对象的时候,由于线程ID与互斥对象内部的线程ID不匹配,所以该互斥对象不会被释放。
当同一个线程在请求成功互斥对象之后,又一次请求了互斥对象,由于相同ID的线程请求同一个互斥对象,系统默认是能够请求成功的,那么互斥对象怎么知道自己被请求了几次呢?这是通过互斥对象内部的计数器来完成的。
场景1:
trhead1:先请求成功了互斥对象,输出一句话,没有释放互斥对象。
trhead2:请求互斥对象,输出一句话。
结果:
main函数结束,两句话都会被输出。
分析:当main函数结束时,进程结束,所有的子线程会被终止。当thread1被终止时,互斥对象被释放,其内部的线程ID会被置为NULL,此时thread2请求互斥对象成功,执行完自己的代码退出。
怎么知道互斥对象是正常请求到的,还是一个线程终止时互斥对象被释放时请求到的呢?
看WaitForSingleObject函数的返回值,该函数有三个返回值:WAIT_ABANDONED、WAIT_OBJECT_0、WAIT_TIMEOUT。
WAIT_ABANDONED:线程异常终止,互斥对象才被设置为有信号状态。此时,共有资源的状态是不确定的,需要做相应的处理。
WAIT_OBJECT_0:请求的互斥对象是有信号状态,即正常请求到的。
WAIT_TIMEOUT
如果WAIT_ABANDONED导致互斥对象被释放,则需要考虑共享资源的状态问题。
场景2:
怎么让一个应用程序只能开启一次?
原理:命名互斥对象只能被创建一次。
void main()
{
HANDLE hThread,hThread2;
hThread = CreateThread(NULL,0,Func1,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,Func2,NULL,0,NULL);
CloseHandle(hThread);
CloseHandle(hThread2);
//如果第二个参数设置为TRUE的话,表示当前创建互斥对象的线程(主线程)拥有了互斥对象,而其没有释放,所以,其他的线程请求互斥对象的时候只能处于等待状态。
//hMutex =CreateMutex(NULL,FALSE,NULL);
//hMutex =CreateMutex(NULL,TRUE,NULL);
///////////////////////////////////////////////////////////////
//创建一个有名字的互斥对象。
hMutex =CreateMutex(NULL,TRUE,L"tickets");
if(hMutex)
{
if(ERROR_ALREADY_EXISTS == GetLastError())//先前已经创建了该命名互斥对象,即已经有应用程序的实例存在了。
{
cout<<"only one instance can run\n";
return;
}
}
///////////////////////////////////////////////////////////////
WaitForSingleObject(hMutex,INFINITE);
ReleaseMutex(hMutex);
ReleaseMutex(hMutex);
Sleep(4000);
system("pause");
}