InterlockedIncrement
函数的作用:
在多线程同时对一个变量访问时,保证一个线程访问变量时其他线程不能访问
- 事件是很常用的多线程同步互斥机制
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SECURITY_ATTRIBUTES结构指针,可为NULL
BOOL bManualReset, // 手动/自动
// TRUE:表示手动,在WaitForSingleObject后必须手动调用ResetEvent清除信号
// FALSE:表示自动,在WaitForSingleObject后,系统自动清除事件信号
BOOL bInitialState, //初始状态,FALSE为无信号,TRUE为有信号
LPCTSTR lpName //事件的名称
);
下边是使用演示:
CreateThread
- 采用CreateThread()创建多线程程序,参考https://blog.csdn.net/cbnotes/article/details/8277180/
- 多线程实例
在此将写一个简单的多线程程序,用以展示多线程的功能和使用方法。该程序的主要的思想是画3个进度条,分别以多线程和单线程方式完成,大家可以比较一下。
说明:
(1)该程序还将和单线程做对比。
(2)由于给线程的函数传递了多个参数,所以采用结构体的方式传递参数。
(3)为了演示效果,采用了比较耗时的打点处理。
//线程函数声明
DWORD WINAPI ThreadProc(LPVOIDlpParam);
//为了传递多个参数,我采用结构体
struct threadInfo
{
HWND hWnd; //窗口句柄
int nOffset; //偏移量
COLORREF clrRGB; //颜色
};
protected:
HANDLE hThead[3]; //用于存储线程句柄
DWORD dwThreadID[3];//用于存储线程的ID
threadInfo Info[3]; //传递给线程处理函数的参数
//----> 代码实现
//单线程测试
void CMultiThread_1Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
//使能按钮
GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE);
CDC *dc = GetDC();
CRect rt;
GetClientRect(rt);
dc->FillSolidRect(0,0,rt.Width(),rt.Height()-70,RGB(240,240,240));//刷新背景
dc->TextOut(97,470,"#1");
dc->TextOut(297,470,"#2");
dc->TextOut(497,470,"#3");
//#1
for (int i=0;i<460;i++)
{
for (int j =10 ;j<200;j++)
{
dc->SetPixel(j,460-i,RGB(255,0,0));
}
}
//#2
for (int i=0;i<460;i++)
{
for (int j =210 ;j<400;j++)
{
dc->SetPixel(j,460-i,RGB(0,255,0));
}
}
//#3
for (int i=0;i<460;i++)
{
for (int j =410 ;j<600;j++)
{
dc->SetPixel(j,460-i,RGB(0,0,255));
}
}
ReleaseDC(dc);
//使能按钮
GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);
}
//多线程测试
void CMultiThread_1Dlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CDC *dc = GetDC();
CRect rt;
GetClientRect(rt);
dc->FillSolidRect(0,0,rt.Width(),rt.Height()-70,RGB(240,240,240));//刷新背景
dc->TextOut(97,470,"#1");
dc->TextOut(297,470,"#2");
dc->TextOut(497,470,"#3");
//初始化线程的参数
Info[0].hWnd = Info[1].hWnd = Info[2].hWnd = GetSafeHwnd();
Info[0].nOffset = 10;Info[1].nOffset = 210;Info[2].nOffset = 410;
Info[0].clrRGB = RGB(255,0,0);Info[1].clrRGB= RGB(0,255,0);Info[2].clrRGB = RGB(0,0,255);
//创建线程
for (int i = 0;i<3;i++)
{
hThead[i] = CreateThread(NULL,0,ThreadProc,&Info[i],0,&dwThreadID[i]);
}
ReleaseDC(dc);
}
DWORD WINAPI ThreadProc(LPVOIDlpParam)
{
threadInfo*Info = (threadInfo*)lpParam;
CDC *dc = CWnd::FromHandle(Info->hWnd)->GetDC();
for (int i=0;i<460;i++)
{
for (int j=Info->nOffset;j<Info->nOffset+190;j++)
{
dc->SetPixel(j,460-i,Info->clrRGB);
}
}
DeleteObject(dc);
return 0;
}
测试效果如下:
GetMessage
- GetMessage是从调用线程的消息队列里取得一个消息并将其放于指定的结构。此函数可取得与指定窗口联系的消息和由PostThreadMessage寄送的线程消息。此函数接收一定范围的消息值。GetMessage不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。
GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)
参数:
lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage为任何属于调用线程的窗口检索消息,
线程消息通过PostThreadMessage寄送给调用线程。
wMsgFilterMin:指定被检索的最小消息值的整数。
wMsgFilterMax:指定被检索的最大消息值的整数。
返回值:如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。
如果出现了错误,返回值是-1。例如,当hWnd是无效的窗口句柄或lpMsg是无效的指针时。
若想获得更多 的错误信息,请调用GetLastError函数。
PeekMessage
- PeekMessage 调用的一个例子:
PeekMessage(&msg,NULL,0,0,PM_REMOVE);
前面的4个参数(一个指向 MSG 结构的指针、一个窗口的句柄、两个值指示消息范围)与 GetMessage 的参数相同。
将第二、三、四个参数设置为 NULL 或 0时,表明我们想让 PeekMessage 返回程序中所有窗口的所有消息。
如果要将消息从消息队列中删除,则将 PeekMessage 的最后一个参数设置为 PM_REMOVE。
如果不希望删除消息,则将最后一个参数设置为 PM_NOREMOVE,这使得程序可以检查程序的消息队列中的下一个消息,而不实际删除它。
GetMessage 不将控制返回给程序,直到从程序的消息队列中获取消息,但是 PeekMessage 总是立刻返回,而不论一个消息是否出现。
当(应用程序的)消息队列中有一个消息时,PeekMessage 的返回值为 TRUE(非0),并且将按通常方式处理消息。当队列中没有消息时,PeekMessage 返回 FALSE(0)。
考虑如下例子:
// 普通的消息循环
while( GetMessage(&msg,NULL,0,0) )
{
TranslateMessage(&msg);
DispatchMessage (&msg);
}
return msg.wParam;
// 等价于
while( TRUE )
{
if( PeekMessage(&msg,NULL,0,0,PM_REMOVE) )
{
if(msg.message == WM_QUIT)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// Other program lines to do some work
}
}
return msg.wParam;
如果 PeekMessage 的返回值为 TRUE,则消息按通常方式进行处理。如果返回值为 FALSE,则在将控制返回给Windows(操作系统) 之前,还可以做一点工作(如显示另一个随机矩形)。
PeekMessage 不能从消息队列中删除 WM_PAINT 消息。
从队列中删除 WM_PAINT 消息的唯一方法是令窗口客户区的失效区域变得有效,这可以用 ValidateRect 和 ValidateRgn 或者 BeginPaint 和 EndPaint 对来完成。
不能使用如下所示的代码来清除消息队列中的所有消息:
while( PeekMessage(&msg,NULL,0,0,PM_REMOVE) );
这条语句从消息队列中删除 WM_PAINT 之外的所有消息。如果队列中有一个 WM_PAINT 消息,程序就会永远地陷在 while 循环中。
WaitForSingleObject
- 等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);
当线程调用该函数时,第一个参数hObject标识一个能够支持被通知/未通知的内核对象。第二个参数dwMilliseconds.允许该线程指明,为了等待该对象变为已通知状态,它将等待多长时间。
- 调用下面这个函数将告诉系统,调用函数准备等待到hProcess句柄标识的进程终止运行为止:
WaitForSingleObject(hProcess, INFINITE);
第二个参数告诉系统,调用线程愿意永远等待下去(无限时间量),直到该进程终止运行。
通常情况下, INFINITE是作为第二个参数传递给WaitForSingleObject的,不过也可以传递任何一个值(以毫秒计算)。顺便说一下, INFINITE已经定义为0xFFFFFFFF(或-1)。当然,传递INFINITE有些危险。如果对象永远不变为已通知状态,那么调用线程永远不会被唤醒,它将永远处于死锁状态,
不过,它不会浪费宝贵的CPU时间。
- 下面是如何用一个超时值而不是INFINITE来调用WaitForSingleObject的例子:
DWORD dw = WaitForSingleObject(hProcess, 5000);
switch(dw)
{
case WAIT_OBJECT_0:
// The process terminated.
break;
case WAIT_TIMEOUT:
// The process did not terminate within 5000 milliseconds.
break;
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
}
上面这个代码告诉系统,在特定的进程终止运行之前,或者在5 0 0 0 m s时间结束之前,调用线程不应该变为可调度状态。因此,如果进程终止运行,那么这个
函数调用将在不到5000ms的时间内返回,如果进程尚未终止运行,那么它在大约5000ms时间内返回。注意,不能为dwMilliseconds传递0。如果传递了0,WaitForSingleObject函数将总是立即返回。WaitForSingleObject的返回值能够指明调用线程为什么再次变为可调度状态。如果线程等待的对象变为已通知状态,那么返回值是WAIT_OBJECT_0。如果设置的超时已经到期,则返回值是WAIT_TIMEOUT。如果将一个错误的值(如一个无效句柄)传递给WaitForSingleObject,那么返回值将是WAIT_FAILED(若要了解详细信息,可调用GetLastError)。