1.继续 计算 项目
1.1 更改发送事件信号来控制线程,使用消息来控制线程
1.在线程处理函数 DWORD WINAPI CThreadDlg::ThreadProc 中注释掉事件信号等待语句;
2.在 stdafx.h 头文件中添加一行自定义消息: #define UM_THREAD (WM_USER+1) ;
3.在原来等待事件信号处获取消息: MSG msg ; ::GetMessage(&msg, 0, 0, 0) ;
4.在 计算 按钮的函数中发送消息: ::PostThreadMessage(m_dwThreadID, UM_THREAD, 100, 0) (不用SendMessage或PostMessage的原因是因为这两个函数需要窗口句柄,但是线程并没有窗口句柄,因此无法使用了;其中的 100 是一个参数,就是随着 UM_THREAD 消息传送的参数);
DWORD WINAPI CThreadDlg::ThreadProc(_In_ LPVOID lpParameter)
{
CThreadDlg* pThis = (CThreadDlg*)lpParameter;
while (pThis->m_QuitFlag)
{
// =================等待================
//::WaitForSingleObject(pThis->m_hEvent, INFINITE);
// =================等待================
// 3-5
// 1.使用消息来控制线程
// ================等待消息=============
MSG msg;
::GetMessage(&msg, 0, 0, 0);
if(msg.message == UM_THREAD)
TRACE("%d\n", msg.wParam);
// ================等待消息=============
int n_sum = 0;
// =============计算===================
for(int i=1; i<=pThis->m_nSumMaxValue; i++)
n_sum += i;
// =============计算===================
//// ===============放到编辑框中===========
CString str;
str.Format(_T("%d"), n_sum);
pThis->m_show_sum.SetWindowText(str);
// ===============放到编辑框中===========
}
return 0;
}
void CThreadDlg::OnBnClickedButton2()
{
// ::SetEvent(m_hEvent);
// 3-5
// 1.使用消息来控制线程
::PostThreadMessage(m_dwThreadID, UM_THREAD, 100, 0);
}
1.2 一般不能够在工作线程中使用主线程中的控件
1.我们将 CThreadDlg::ThreadProc 函数中放到 Edit 控件上的语句注释掉;
2.我们通过给 App (也就是主线程)发消息来将结果放到 Edit 控件上;
3.首先在 CThreadDlg::ThreadProc 函数原来设置 Edit 控件处给 App 主线程发消息: ::PostThreadMessage(theApp.m_nThreadID, UM_THREAD_TO_MAIN, n_sum, 0) (UM_THREAD_TO_MAIN 是我们在 stdafx.h 中定义的自定义消息: #define UM_THREAD_TO_MAIN (WM_USER+2);第一个参数是主线程的ID,通过 theApp 来直接获得);
4.在 App 类中添加一个自定义消息处理函数: afx_msg void OnShowEdit(WPARAM wParam, LPARAM lParam) (这里返回值是 void 而不是 LRESULT 的原因是我们这个消息处理函数处理的是由工作线程发送的消息,因此我们在消息映射表中添加的是 ON_THREAD_MESSAGE 宏,其需求的第一个参数是 void 型的函数指针,因此自定义的消息处理函数也应该是 void 返回值);
5.在 CThreadApp::OnShowEdit 函数中,我们获取 str ,并调用主线程中的对象来将 str 放到 Edit 控件中;
BEGIN_MESSAGE_MAP(CThreadApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
// 1.5 使用消息来处理,给主线程发送消息
ON_THREAD_MESSAGE(UM_THREAD_TO_MAIN, &CThreadApp::OnShowEdit)
END_MESSAGE_MAP()
// 1.5 使用消息来处理,给主线程发送消息
// 主线程消息处理
void CThreadApp::OnShowEdit(WPARAM wParam, LPARAM lParam)
{
CString str;
str.Format(_T("%d"), wParam);
// 获取主窗口的句柄
CThreadDlg* pDlg = (CThreadDlg*)m_pMainWnd; // 这里需要强转的原因是 m_pMainWnd 的类型是 CWnd 类型,是父指针类型,无法调用子类中的成员,因此需要进行强转
pDlg->m_show_sum.SetWindowText(str);
}
2.火车票销售项目(知识点:线程同步)
2.1 点击卖票则创建10个线程
1.创建一个基于对话框的MFC应用程序,并在其上添加一个按钮 开始卖票 和一个 List Box 控件;
2.点击 开始卖票 按钮则创建10个线程;
3.给主对话框类添加成员变量: HANDLE m_hThread[10] ; LONG m_iAllTicket ; bool m_bQuitFalg ;在构造函数中 m_iAllTicket = 100 ; m_bQuitFalg = true ; ::ZeroMemory(m_hThread, sizeof(HANDLE)*10) ; ZeroMemory 函数是初始化一块内存的函数;
4.在 开始卖票 按钮中创建10个线程,for 循环;
5.在线程处理函数 CSellTicketDlg::ThreadProc 中设置卖票;
6.在 CSellTicketDlg::OnClose 函数中循环关闭每个线程句柄;
7.修改 List Box 的 Sort 属性为 False ;
CSellTicketDlg::CSellTicketDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CSellTicketDlg::IDD, pParent)
{
m_iAllTicket = 100;
m_bQuitFalg = true;
::ZeroMemory(m_hThread, sizeof(HANDLE)*10);
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CSellTicketDlg::OnBnClickedButton1()
{
// 创建10个线程
for(int i=0; i<10; i++)
{
m_hThread[i] = ::CreateThread(0, 0, &CSellTicketDlg::ThreadProc, this, 0, 0);
if(m_hThread[i] == 0)
MessageBox(_T("创建线程失败"));
}
}
// 线程处理函数
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;
while (pThis->m_bQuitFalg)
{
CString str;
str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);
pThis->m_lstbox.AddString(str);
pThis->m_iAllTicket--;
if(pThis->m_iAllTicket <= 0)
{
break;
}
}
return 0;
}
void CSellTicketDlg::OnClose()
{
m_bQuitFalg = false;
for(int i=0; i<10; i++)
{
if(::WaitForSingleObject(m_hThread[i], 10) == WAIT_TIMEOUT)
::TerminateThread(m_hThread[i], -1);
::CloseHandle(m_hThread[i]);
m_hThread[i] = 0;
}
CDialogEx::OnClose();
}
2.2 运行上述代码后,我们发现生成的卖票信息会有重复的,就是有第100张票卖了好几次的现象
2.3 在多个线程中共享变量,那么这个变量一定需要加锁
2.4 解决 2.2 中的问题,我们需要使用 原子访问 的方法,并加上 旋转锁
1.在主对话框类的cpp文件中定义一个全局变量 LONG bflag = FALSE ;
2.在线程处理函数中,当有线程进入这个函数时,将 bflag 的值用原子访问方式更改为 TRUE ,在对 m_iAllTicket 变量进行 – 操作时也更改为原子访问的方式;
// 2.11 加旋转锁
LONG bflag = FALSE; // 没有线程进入的标志
// 线程处理函数
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;
while (pThis->m_bQuitFalg)
{
// 2.11 旋转锁
// ======================================
if(::InterlockedExchange(&bflag, TRUE) == TRUE)
continue;
// ======================================
CString str;
str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);
pThis->m_lstbox.AddString(str);
//pThis->m_iAllTicket--;
// 2.10 使用原子访问操作来对变量加锁
::InterlockedDecrement(&(pThis->m_iAllTicket));
if(pThis->m_iAllTicket <= 0)
{
::InterlockedExchange(&bflag, FALSE);
break;
}
// =================
::InterlockedExchange(&bflag, FALSE);
// =================
}
return 0;
}
3.原子访问只能对变量进行,不能对代码进行;
4.给代码段加锁时需要注意加锁的力度,不能将太多的公共代码加锁,否则会导致程序卡顿;
5.旋转锁的合理等待次数是 4000 回,若 4000 回仍未进入线程处理函数则切换到内核模式去等待;