火车站售票系统模拟
#include
#include
DWORD WINAPI ThreadProc1( LPVOID lpParameter); //售票窗口1
DWORD WINAPI ThreadProc2( LPVOID lpParameter); //售票窗口2
int tickets = 1; //票号,从第一张票票号为1
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
Sleep(5000);
}
//线程1的入口函数(售票窗口1)
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while (TRUE)
{
if (tickets <= 100)//卖超过100张就结束
{
cout << "thread1 sell ticket : " << tickets ++ << endl;
}
else
break;
}
return 0;
}
//线程2的入口函数(售票窗口2)
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while (TRUE)
{
if (tickets <= 100)
{
cout << "thread2 sell ticket : " << tickets ++ << endl;
}
else
break;
}
return 0;
}
程序隐患:
事实上,上述程序存在隐患,比如以下情况:
当tickets为100时,线程1函数进入 if 语句块后,正好该线程的时间片到了,操作系统就会选择线程2让其进行,而这时变量tickets的值还没有加1,因此这时变量tickets的值仍是100,线程2进入它的if 语句块中,于是线程2执行卖票操作,打印票号100,然后tickets变量加1,其值变为101。如果当线程2执行完成上述操作之后,正好又轮到线程1开始运行了。而这时线程1将从原先的if 语句块开始执行,于是它输出当前的票号,而此时tickets变量的值已经是101了,也就是说,我们会看到线程1卖了号码为101的票。显然这种情况时不允许的。
上述问题的出现主要原因就是两个线程访问了同一个全局变量:ticket。为了避免这种问题的发生,就要求在多个线程之间进行一个同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源。
线程需要在下面两种情况下互相通信,以实现同步:
线程同步原理
互斥对象(Mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。 创建互斥对象需要调用函数:CreateMutex
互斥对象就相当于上图的那把锁。
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //通常设为NULL,该线程使用默认的安全性
BOOL bInitialOwner,//可设为FALSE,创建这个互斥对象的线程不获得其所有权。
LPCTSTR lpName//指定互斥对象的名称。如果此参数为NULL,则创建一个匿名的互斥对象。
);
获得互斥对象的所有权
线程必须主动请求共享对象的使用权才能获得该所有权,这可以通过调用WaitForSingleObject
函数实现。
DWORD WatiForSingleObject(HANDLE hHandle, DWORD dwMillisecond); //(锁定+等待)
HANDLE hHandle
所请求对象的句柄。本例为互斥对象句柄:hMutex。一旦互斥对象处于有信号状态,则该函数返回,接着,操作系统会将这个互斥对象设为无信号状态。如果该互斥对象处于无信号状态,则该函数会一直等待,这样会暂停线程的执行。
DWORD dwMillisecond
指定等待的时间,如果指定的时间间隔已过,即使所请求的对象处于无信号状态,该函数也返回。如果该参数为0,该函数立即返回。如果该参数为INFINTE,则该函数永远等待,直到互斥对象处于有信号状态才返回。
释放互斥对象的所有权
当线程对共享资源访问结束后,应释放互斥对象的所有权,让该对象处于有信号状态。这时需要调用函数:ReleaseMutex
BOOL ReleaseMutex(HANDLE hMutex);//(解锁)
完整的卖票程序代码:
#include
#include
DWORD WINAPI ThreadProc1( LPVOID lpParameter);
DWORD WINAPI ThreadProc2( LPVOID lpParameter);
int tickets = 1;
HANDLE hMutex;//锁定义为全局对象
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
hMutex = CreateMutex(NULL, FALSE, NULL);//创建锁的对象不具有锁的所有权,注意第二个参数一定要设为False
Sleep(5000);
}
//线程1的入口函数(售票窗口1)
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);//(锁上 + 等待)
if (tickets <= 100)
{
Sleep(1);
cout << "thread1 sell ticket : " << tickets ++ << endl;
}
else
break;
ReleaseMutex(hMutex);//(解锁)
}
return 0;
}
//线程2的入口函数(售票窗口2)
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while (TRUE)
{
WaitForSingleObject(hMutex,INFINITE);//(锁上 + 等待)
if (tickets <= 100)
{
Sleep(1);
cout << "thread2 sell ticket : " << tickets ++ << endl;
}
else
break;
ReleaseMutex(hMutex);//(解锁)
}
return 0;
}
临界区对象
临界区,也称关键代码段,它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。
通常把多线程中访问统一资源的那部分代码当作临界区,从而达到线程同步的目的。
相关API函数
初始化临界区对象:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSetion);
获得临界区对象所有权:
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSetion);
释放临界区对象所有权:
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSetion);
释放临界区对象:
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSetion);
利用临界区完成上述售票系统
#include
#include
DWORD WINAPI ThreadProc1( LPVOID lpParameter);
DWORD WINAPI ThreadProc2( LPVOID lpParameter);
int tickets = 1;
CRITICAL_SECTION cs;//创建一把锁
void main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
hThread2 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
InitializeCriticalSection(&cs);//把这把锁初始化
Sleep(5000);
}
//线程1的入口函数(售票窗口1)
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while (TRUE)
{
EnterCriticalSection(&cs);//获取临界区对象所有权
if (tickets <= 100)
{
Sleep(1);
cout << "thread1 sell ticket : " << tickets ++ << endl;
}
else
break;
LeaveCriticalSection(&cs);//释放临界区对象所有权
}
return 0;
}
//线程2的入口函数(售票窗口2)
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while (TRUE)
{
EnterCriticalSection(&cs);//获取临界区对象所有权
if (tickets <= 100)
{
Sleep(1);
cout << "thread2 sell ticket : " << tickets ++ << endl;
}
else
break;
LeaveCriticalSection(&cs);//释放临界区对象所有权
}
return 0;
}
互斥对象与临界区的比较
互斥对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象这样的内核对象,可以在多个进程中的各个线程间进行同步。
临界区只能在同一进程内的线程间进行同步,但使用最简单,同步速度较快,因此是实现同步化的首选方法。在使用临界区时,由于在等待进入关键代码段时无法设定超时值,容易进入死锁状态。