基本概念
进程
进程是指的打开的.exe程序,比如点击steam.exe,这就是打开了一个进程
当然你可以同时打开很多个steam
在steam上,我们也可以打开很多个附带的程序
所以,进程和程序是n对n的关系
进程由两部分别组成
操作系统用来管理进程的内核对象,是封装的,只有调用windows的函数才能访问
地址空间,这个很好理解
进程本身不执行任何东西,它仅仅知识一个存放线程的容器,真正执行的是线程
线程
线程也分为两部分,内核对象和线程栈
每个线程都会被分配一个时间片,只有在时间片的时间内,才会运行
多线程,顾名思义,即为多个线程运行,注意,在一个时间点,只会有一个线程在运行,我们之所以感觉是一起运行,是因为时间片太短
线程的创建
通过CreateThread函数,
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
DWORD dwStackSize, // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // thread argument
DWORD dwCreationFlags, // creation option
LPDWORD lpThreadId // thread identifier
);
一般来讲除了第三个参数LPTHREAD_START_ROUTINE是方法名,第四个是传入的参数外,其他几个不是null就是0.如
HANDLE h;
h = CreateThread(NULL,0,Fun1,(LPVOID)j,0,NULL);
Fun1就是入口函数名,(LPVOID)j,j就是要传入的数据,不过使用时一般要转换类型
实现线程的入口函数
DWORD WINAPI CNewTestDlg::Fun1(LPVOID lp1){
//这里写具体实现代码
}
在MFC上自动添加该函数时要注意,该函数的返回是DWORD,WINAPI要建完后加入
LPVOID是返回的参数类型,lp1是变量名称,自己随便取,不过为了可读性,推荐以下取名lpParameter
然后建完线程需要关闭
CloseHandle(h);
以下是一个简单的线程实现代码
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
void main()
{
HANDLE h1;
h1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
CloseHandle(h1);
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter){
cout<<"hello"<<endl;
}
最后会输出hello
多线程同时运行
这里的同时运行并不是真的同一时间,而是交替运行,只是交替时间很短,人眼无法分别
一共有三种方法实现
互斥对象、事件对象、关键代码段
互斥对象
//创建互斥对象
HANDLE hMutex = CreateMutex(NULL,FALSE,NULL);
//释放
ReleaseMutex(hMutex );
例子
void main()
{
HANDLE h1;
HANDLE h2;
HANDLE hMutex = CreateMutex(NULL,FALSE,NULL);
h1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
h2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
CloseHandle(h1);
CloseHandle(h2);
ReleaseMutex(hMutex );
}
代码放置位置如上
事件对象
事件对象有两种类型:人工重置和自动重置
人工重置的事件对象得到通知后,等待该实践对象的所有线程均变为可调度线程
自动重置的事件对象得到通知后,等待该实践对象的线程只有一个变为可调度线程
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset, // reset type
BOOL bInitialState, // initial state
LPCTSTR lpName // object name);
第一个是结构体指针,null时使用默认的安全性
第二个是为bool型,true时为人工重置的事件对象,false为自动重置的事件对象
第三个是bool型,true时为有信号,false无信号
第四个是事件对象的名称,null时,默认为匿名信件
SetEvent函数,将指定事件对象设置为有信号
ResetEvent函数,将指定事件对象设置为无信号
两个函数均为bool型
关键代码段
也称临界区,它是指小段代码,在代码能够执行前,它必须独占某些资源的访问权
就像公共厕所,使用前,必须判断里面有没有人,没人才能上厕所
实现依赖于InitializeCriticalSection(LPCRITICAL_SECTION 变量名)函数
该函数只有一个参数,指向CriticalSection的指针,通过这个来实现判断
但是这种方式可能出现线程死锁
三者的对比
互斥对象和事件对象都是内核对象,速度较慢,但是可以在多个进程中的各个线程间进行同步
关键代码段速度快,但是容易死锁