1.多线程程序容易出现的问题
多线程程序中,多个线程在申请唯一份资源时,存在一个隐患那就是重复使用。举一个例子,火车售票系统,一共100张票,用两个线程来模拟售票。
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while (true)
{
if (gTickets > 0)
{
Sleep(10); //为了展现两个线程卖同一张票的情况,这里通过暂停线程实现。
std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while (true)
{
if (gTickets > 0)
{
Sleep(10); //为了展现两个线程卖同一张票的情况,这里通过暂停线程实现。
std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
}
return 0;
}运行结果:
出现这样的结果也很好解释,因为每个线程执行都有一个时间片,本例的情况是,当线程2刚好陷入if判断条件之后。线程2时间刚好用完,轮到线程1执行,当线程1执行完之后,时间片刚好轮到线程1,线程1就执行之后的语句就出现0号票的情况。
2.利用互斥对象实现线程的同步
互斥对象(mutex)属于内核对象,它能确保线程拥有对单个资源的互斥访问权限。互斥对象包含3部分,一个使用量,一个线程ID和一个计数器。其中ID用户识别系统中那个线程当前拥有互斥对象,计数器用户指明该线程拥有互斥对象的次数。
HANDLE WINAPI CreateMutex( __in LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, __in LPCTSTR lpName );lpThreadAttributes 指向SECURITY_ATTRIBUTES结构指针,如果传递NULL,则表示线程使用默认的安全性。
bInitialOwner
指互斥对象初始的拥有者。如果为真,则创建这个互斥对象的线程获得对象的所有权。否则,没有线程获得所有权。lpName
指定互斥对象的名称。如果为NULL,则创建一个匿名的互斥对象。否则,当调用成功,如果创建的互斥对象存在将返回该命名存在的互斥对象句柄。这时调用GetLastError函数将返回ERROR_ALREADY_EXISTS当线程对共享资源访问结束后,应该释放该对象的所有权,让该对象处于已通知状态,这是需要调用ReleaseMutex函数。
BOOL WINAPI ReleaseMutex( __in HANDLE hMutex );传入互斥对象的句柄,如果调用成功,返回非0值,否则返回0值。
线程必须主动请求共享对象的使用权才有可能获得所有权,这是通过WaitForSingleObject 函数实现,函数声明如下:
DWORD WINAPI WaitForSingleObject( __in HANDLE hHandle, __in DWORD dwMilliseconds );hHandle所请求对象的句柄。一旦互斥对象处于有信号状态,则该函数就返回。否则将暂停线程的执行。
dwMilliseconds
指定等待的时间间隔,以毫秒为单位。如果时间间隔已过,函数立即返回。如果设置参数为0,那么函数测试对象状态立即返回;如果设置INFINITE,则函数一直等待,直到等待对象有信号状态才会返回。
利用互斥对象来实现线程同步:
#include <Windows.h>
#include <iostream>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int gTickets(100);
HANDLE hMutex(NULL);
int main()
{
HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread2);
hMutex = CreateMutex(NULL, FALSE, NULL);
Sleep(4000);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while (true)
{
//等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
WaitForSingleObject(hMutex, INFINITE);
if (gTickets > 0)
{
Sleep(10);
std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while (true)
{
//等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
WaitForSingleObject(hMutex, INFINITE);
if (gTickets > 0)
{
Sleep(10);
std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
实例2:
#include <Windows.h>
#include <iostream>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
int gTickets(100);
HANDLE hMutex(NULL);
int main()
{
HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread2);
// hMutex = CreateMutex(NULL, FALSE, NULL);
hMutex = CreateMutex(NULL, TRUE, NULL);
Sleep(4000);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
while (true)
{
ReleaseMutex(hMutex);
//等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
WaitForSingleObject(hMutex, INFINITE);
if (gTickets > 0)
{
Sleep(10);
std::cout << "thread1 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
while (true)
{
ReleaseMutex(hMutex);
//等待互斥对象传递的信号,而且WaitForSingleObject必须放这里
WaitForSingleObject(hMutex, INFINITE);
if (gTickets > 0)
{
Sleep(10);
std::cout << "thread2 sell ticket: " << gTickets-- <<std::endl;
}
else
{
break;
}
ReleaseMutex(hMutex);
}
return 0;
}
我们得到的结果是没有打印任何信息。那是因为对互斥对象来说,谁拥有谁释放。所以当主线程创建互斥对象没有释放,那么线程1,线程2就不能释放互斥对象,因为互斥对象记录的线程ID是主线程的。所以线程1,线程2调用ReleaseMutex(hMutex)函数没有用,即子线程都在自己的时间片期间等待。
实例3:
只改动上面例子的mai函数,修改如下:
int main()
{
HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread2);
// hMutex = CreateMutex(NULL, FALSE, NULL);
hMutex = CreateMutex(NULL, TRUE, NULL);
WaitForSingleObject(hMutex, INFINITE);
ReleaseMutex(hMutex);
Sleep(4000);
return 0;
}
这时你发现虽然主线程中你最后调用释放互斥对象函数,但是最终结果还是没有变。那是因为当你创建互斥对象并设置bInitialOwner参数为TRUE时,其互斥对象计数器加1,之后调用WaitForSingleObject(hMutex, INFINITE)函数此时的计数器为2。你调用一次释放互斥对象函数最终计数值还是1。所以主线程对互斥对象一直有信号,则子线程一直处于等待情况。
实例4:
#include <Windows.h>
#include <iostream>
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
HANDLE hMutex(NULL);
int main()
{
HANDLE hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread2);
hMutex = CreateMutex(NULL, true, NULL);
WaitForSingleObject(hMutex, INFINITE);
ReleaseMutex(hMutex);
ReleaseMutex(hMutex);
Sleep(4000);
return 0;
}
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
WaitForSingleObject(hMutex, INFINITE);
std::cout << "thread1 is running" << std::endl;
return 0;
}
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
WaitForSingleObject(hMutex, INFINITE);
std::cout << "thread2 is running" << std::endl;
return 0;
}
这种情况你会发现线程1,线程2都只执行一次。这是因为当操作系统一旦发现该线程已经终止,它就会自动将该线程拥有的互斥对象重新初始化,计数器为0,线程ID为0。