多线程程序的基础流程
1、声明线程入口函数
DWORD WINAPI ThreadProc(
LPVOID lpParameter
); //注意这里ThreadProc这个名字是可以按自己的要求修改的;
2、在主函数中为线程入口函数创建线程
HANDLE WINAPI CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全性
SIZE_T dwStackSize, // 初始栈的大小
LPTHREAD_START_ROUTINE lpStartAddress, // 线程入口函数的地址
LPVOID lpParameter, //线程入口函数的形参
DWORD dwCreationFlags, //线程创建后是否立即运行
LPDWORD lpThreadId
);
3、实现线程入口函数
以下就是用多线程实现的一个卖票程序
//用到了创建线程等windows的函数一次要包含相应头文件
#include<Windows.h>
#include<iostream>
//这里需要注意若不加使用标准命名空间的话会使得I/O函数无法识别
using namespace std;
//建立一个不含线程同步的基础多线程买票程序
//共有100z张可供销售的车票
int tickets=100;
int index=0;
//声明线程入口函数
DWORD WINAPI Fun1Proc(LPVOID);
DWORD WINAPI Fun2Proc(LPVOID);
void main()
{
HANDLE hThread1; //用于保存线程的句柄
HANDLE hThread2; //用于保存线程的句柄
//创建线程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
//关闭句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//使主线程挂起4秒,以便其他线程获取执行权限
Sleep(4000);
}
//线程1的入口函数
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while(TRUE)
{
if(tickets>0)
{
//Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
}
else
break;
}
return 0;
}
//线程2入口函数
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while(TRUE)
{
if(tickets>0)
{
//Sleep(1);
cout<<"Thread2 sells "<<tickets--<<endl;
}
else
break;
}
return 0;
}
上面的程序虽然可以使用多线程的运行,但这里面存在着一个潜在的隐患,就是当票数还剩一张时,由于tickets>1是由于大于0因此进入if条件语句,而若线程1执行到Sleep(1)初时他的时间片到点了,换成线程2执行。此时由于没有执行cout语句因此tickets仍然为1,而线程2执行后ticket是变为0。此时线程1开始从挂起处开始执行,虽然对此事没有票了,但由于是在挂起处执行的因此会继续执行后面的语句造成出现存票为0但仍然可以买票的bug。
为了解决上述的问题,需要对线程进行同步,线程同步有四种方式:事件对象(Event)、互斥对象(Mutex)、关键代码段(CriticalSection)
1、互斥对象
建立互斥对象句柄
//声明线程入口函数
DWORD WINAPI Fun1Proc(LPVOID);
DWORD WINAPI Fun2Proc(LPVOID);
//建立互斥对象句柄
HANDLE hMutex;
在主线程中创建互斥对象
hMutex=CreateMutex(NULL,FALSE,NULL); //创建互斥对象
//创建线程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
在线程过程函数中,等待互斥对象有信号,在有信号后执行,最后释放互斥对象
while(TRUE)
{
//等待互斥对象变为有信号;
WaitForSingleObject(hMutex,INFINITE);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
}
else
break;
//释放互斥对象,使互斥对象变为有信号状态
ReleaseMutex(hMutex);
}
2、事件对象
创建事件对象句柄
//建立事件对象句柄
HANDLE hEvent;
在主线程中创建事件对象,初始为自动重置和有信号状态,并在最后关闭句柄
hEvent=CreateEvent(NULL,FALSE,TRUE,NULL);//创建互斥对象
//创建线程
hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);
hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);
//关闭句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//使主线程挂起4秒,以便其他线程获取执行权限
Sleep(4000);
CloseHandle(hEvent);
在线程过程函数中实现
while(TRUE)
{
//等待事件对象所有权;
WaitForSingleObject(hEvent,INFINITE);
//将事件设置为有信号状态
ResetEvent(hEvent);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
SetEvent(hEvent);
}
else
break;
//将事件设置为无信号状态
SetEvent(hEvent);
}
3、关键代码段
创建关键代码段句柄
CRITICAL_SECTION g_cs;
在主线程中初始化关键代码段
//初始化关键代码段
InitializeCriticalSection(&g_cs);
在线程过程函数中进入关键代码段,执行并离开关键代码段,进入与离开必须成套执行,否则其他线程就无法执行
while(TRUE)
{
//进入关键代码段
EnterCriticalSection(&g_cs);
if(tickets>0)
{
Sleep(1);
cout<<"Thread1 sells "<<tickets--<<endl;
//离开关键代码段
LeaveCriticalSection(&g_cs);
}
else
{
//离开关键代码段
LeaveCriticalSection(&g_cs);
break;
}
}