操作系统与网络 2019-3-5

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 回仍未进入线程处理函数则切换到内核模式去等待;

猜你喜欢

转载自blog.csdn.net/weixin_42896619/article/details/88430967
今日推荐