其他分享
首页 > 其他分享> > 操作系统与网络 2019-3-6

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

作者:互联网

1.继续卖票项目

1.1 多任务环境下各任务间共享的变量应该加关键字 volatile

1.给昨天的 卖票 项目中记录票数的变量添加关键字 volatile ;
2.添加这个关键字是不允许对某个变量进行优化,每次都只能在内存中读取;

volatile LONG m_iAllTicket;

1.2 关键段(临界区)

1.我们昨天使用了原子访问来处理线程间的关系,现在我们使用 关键段(临界区) 来替换掉昨天使用的原子访问方法;
2.复制一份线程处理函数: DWORD WINAPI CSellTicketDlg::ThreadProc ;
3.关键段需要一个结构体 CRITICAL_SECTION cs ;
4.在主对话框的构造函数中初始化这个结构体: ::InitializeCriticalSection(&cs) ;
5.在 CSellTicketDlg::OnClose 函数中删除关键段: ::DeleteCriticalSection(&cs) ;
6.将线程处理函数 DWORD WINAPI CSellTicketDlg::ThreadProc 中有关原子访问操作的过程更改为关键段的操作;
7.将等待旋转锁的操作更改为进入关键段: ::EnterCriticalSection(&(pThis->cs)) ;
8.对总票数我们仍需使用院子访问的函数来进行自减: ::InterlockedDecrement(&(pThis->m_iAllTicket)) ;
9.最后退出关键段时: ::LeaveCriticalSection(&(pThis->cs)) ;

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);
	::InitializeCriticalSection(&cs);					// 3-6 初始化关键段所需结构体
}

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;
	}
	::DeleteCriticalSection(&cs);				// 3-6 删除关键段所需的结构体

	CDialogEx::OnClose();
}

// 3-6 线程处理函数
// 1.使用关键段来处理
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
	CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;

	while (pThis->m_bQuitFalg)
	{
		// 1.关键段
		// ======================================
		// ::EnterCriticalSection(&(pThis->cs));				// 阻塞的进入关键段的函数
		if(::TryEnterCriticalSection(&(pThis->cs)) == FALSE)	// 非阻塞的进入关键段的函数
			continue;
		// ======================================

		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		if(pThis->m_iAllTicket <= 0)
		{
			// 离开关键段
			::LeaveCriticalSection(&(pThis->cs));
			break;
		}
		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		

		CString str;
		str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);

		pThis->m_lstbox.AddString(str);
		// 2.10 使用原子访问操作来对变量自加
		::InterlockedDecrement(&(pThis->m_iAllTicket));
		
		// 离开关键段
		// =================
		::LeaveCriticalSection(&(pThis->cs));
		// =================
	}
	return 0;
}

10.对于进入关键段的函数,分为 阻塞 和 非阻塞 两种,非阻塞的函数可以让其他没有进入关键段的线程去做其他的事情,而不必在关键段入口一直等待;

1.3 将 关键段 操作封装为一个类

1.由于关键段需要一个成员变量 CRITICAL_SECTION cs ,并且这个变量需要初始化和删除,因此我们自然而然的想到使用类的构造和析构来替换掉变量自己的初始化和删除;
2.在主对话框的头文件中创建一个类: CMyLock 类;
3.将初始化函数封装到 CMyLock 类的构造函数中,将删除函数封装到 CMyLock 类的析构函数中;
4.添加两个成员函数: void MyLock 与 void MyUnlock ;
5.在主对话框类中定义该类的对象 CMyLock m_cMyLock ;
6.在线程处理函数中进行对象的调用;

// 2.3 将关键段代码封装为一个类
class CMyLock
{
private:
	CRITICAL_SECTION cs;
public:
	CMyLock()
	{
		::InitializeCriticalSection(&cs);
	}
	~CMyLock()
	{
		::DeleteCriticalSection(&cs);
	}
	void MyLock()
	{
		::EnterCriticalSection(&cs);
	}
	void MyUnlock()
	{
		::LeaveCriticalSection(&cs);
	}
};

// 3-6 线程处理函数
// 2.3 将关键段封装为类来处理
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
	CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;

	while (pThis->m_bQuitFalg)
	{
		// 1.关键段
		// ======================================
		pThis->m_cMyLock.MyLock();
		// ======================================

		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		if(pThis->m_iAllTicket <= 0)
		{
			// 离开关键段
			pThis->m_cMyLock.MyUnlock();
			break;
		}
		// =============================将这段代码放到上面就可以让产生的负数消失=============================


		CString str;
		str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);

		pThis->m_lstbox.AddString(str);
		// 2.10 使用原子访问操作来对变量自加
		::InterlockedDecrement(&(pThis->m_iAllTicket));

		// 离开关键段
		// =================
		pThis->m_cMyLock.MyUnlock();
		// =================
	}
	return 0;
}

1.4 用事件来信号的方式进行锁操作

1.定义一个事件句柄: HANDLE m_hEvent ;
2.在主对话框的构造函数中创建事件句柄: m_hEvent = ::CreateEvent(0, FALSE, FALSE, 0) ;
3.在 CSellTicketDlg::OnClose 中删除句柄: ::CloseHandle(m_hEvent) ; m_hEvent = 0 ;
4.在 CSellTicketDlg::ThreadProc 中等待信号来: ::WaitForSingleObject(pThis->m_hEvent, 10) ;
5.退出时设置一个信号来: ::SetEvent(pThis->m_hEvent) ;
6.在 开始卖票 按钮中设置信号: ::SetEvent(pThis->m_hEvent) ;

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("创建线程失败"));
	}
	
	// 使用事件处理
	::SetEvent(m_hEvent);
}

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("创建线程失败"));
	}
	
	// 使用事件处理
	::SetEvent(m_hEvent);
}

// 3.使用事件来处理
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
	CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;

	while (pThis->m_bQuitFalg)
	{
		// 3.等待事件来消息
		// ======================================
		if(::WaitForSingleObject(pThis->m_hEvent, 10) == WAIT_TIMEOUT)
			continue;
		// ======================================

		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		if(pThis->m_iAllTicket <= 0)
		{
			// 离开时重置为有信号
			::SetEvent(pThis->m_hEvent);
			break;
		}
		// =============================将这段代码放到上面就可以让产生的负数消失=============================

		// =====================给主对话框发消息============================
		::PostThreadMessage(theApp.m_nThreadID, UM_WORK_TO_MAIN, pThis->m_iAllTicket, GetCurrentThreadId());
		// =====================给主对话框发消息============================

		//CString str;
		//str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);
		//pThis->m_lstbox.AddString(str);

		// 2.10 使用原子访问操作来对变量自加
		::InterlockedDecrement(&(pThis->m_iAllTicket));

		// 离开时重置为有信号
		// =================
		::SetEvent(pThis->m_hEvent);
		// =================
	}
	return 0;
}

1.5 用互斥量来处理

1.定义一个互斥量句柄: HANDLE m_hMutex ;
2.在主对话框的构造函数中创建互斥量句柄: m_hMutex = m_hMutex = ::CreateMutex(0, TRUE, 0) (使用互斥量来处理,为 TRUE 时当前创建者拥有这个互斥量;为TRUE时需要释放掉); ::ReleaseMutex(m_hMutex) ;
3.在 CSellTicketDlg::OnClose 中删除句柄: ::CloseHandle(m_hMutex) ; m_hMutex = 0 ;
4.在 CSellTicketDlg::ThreadProc 中等待信号来: ::WaitForSingleObject(pThis->m_hMutex, 10) ;
5.离开时释放掉互斥量: ::ReleaseMutex(pThis->m_hMutex) ;
6.在 开始卖票 按钮中设置信号: ::ReleaseMutex(pThis->m_hMutex) ;

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);
	// ::InitializeCriticalSection(&cs);					// 3-6 初始化关键段所需结构体

	m_hEvent = ::CreateEvent(0, FALSE, FALSE, 0);			// 通过事件来处理
	
	m_hMutex = ::CreateMutex(0, TRUE, 0);			// 使用互斥量来处理,为 TRUE 时当前创建者拥有这个互斥量
	::ReleaseMutex(m_hMutex);						// 为TRUE时需要释放掉
	// m_hMutex = ::CreateMutex(0, FALSE, 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;
	}
	// ::DeleteCriticalSection(&cs);				// 3-6 删除关键段所需的结构体

	::CloseHandle(m_hEvent);						// 通过事件来处理
	m_hEvent = 0;

	::CloseHandle(m_hMutex);						// 使用互斥量来处理
	m_hMutex = 0;

	CDialogEx::OnClose();
}

DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
	CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;

	while (pThis->m_bQuitFalg)
	{
		// 4.等待互斥量来消息
		// ======================================
		if(::WaitForSingleObject(pThis->m_hMutex, 10) == WAIT_TIMEOUT)
			continue;
		// ======================================

		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		if(pThis->m_iAllTicket <= 0)
		{
			// 离开时释放掉互斥量
			::ReleaseMutex(pThis->m_hMutex);
			break;
		}
		// =============================将这段代码放到上面就可以让产生的负数消失=============================

		//=====================给主对话框发消息============================
		int a =  GetCurrentThreadId();
		::PostThreadMessage(theApp.m_nThreadID, UM_WORK_TO_MAIN, pThis->m_iAllTicket,a);
		//=====================给主对话框发消息============================

		//CString str;
		//str.Format(_T("[%d] 这个线程卖了第[%d]张票~"), GetCurrentThreadId(), pThis->m_iAllTicket);
		//pThis->m_lstbox.AddString(str);

		// 2.10 使用原子访问操作来对变量自加
		::InterlockedDecrement(&(pThis->m_iAllTicket));

		// 离开时释放掉互斥量
		// =================
		::ReleaseMutex(pThis->m_hMutex);
		// =================
	}
	return 0;
}

1.6 使用信号量来处理

1.定义一个信号量句柄: HANDLE m_hSemaphore ;
2.在主对话框的构造函数中创建信号量句柄: m_hSemaphore = ::CreateSemaphore(0, 0, 1, 0) (初始化释放0个,最大存在的信号1个);
3.在 CSellTicketDlg::OnClose 中删除句柄: ::CloseHandle(m_hSemaphore) ; m_hSemaphore = 0 ;
4.在 CSellTicketDlg::ThreadProc 中等待信号来: ::WaitForSingleObject(pThis->m_hSemaphore, 10) ;
5.离开时释放掉信号量: ::ReleaseSemaphore(pThis->m_hSemaphore, 1, 0) ;
6.在 开始卖票 按钮中设置信号: ::ReleaseSemaphore(pThis->m_hSemaphore, 1, 0) ;

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);
	// ::InitializeCriticalSection(&cs);					// 3-6 初始化关键段所需结构体

	m_hEvent = ::CreateEvent(0, FALSE, FALSE, 0);			// 通过事件来处理
	
	m_hMutex = ::CreateMutex(0, TRUE, 0);			// 使用互斥量来处理,为 TRUE 时当前创建者拥有这个互斥量
	::ReleaseMutex(m_hMutex);						// 为TRUE时需要释放掉
	// m_hMutex = ::CreateMutex(0, FALSE, 0);

	m_hSemaphore = ::CreateSemaphore(0, 0, 1, 0);	// 使用信号量来处理
}

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("创建线程失败"));
	}
	
	// 使用事件处理
	::SetEvent(m_hEvent);

	// 使用信号量来处理
	::ReleaseSemaphore(m_hSemaphore, 1, 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;
	}
	// ::DeleteCriticalSection(&cs);				// 3-6 删除关键段所需的结构体

	::CloseHandle(m_hEvent);						// 通过事件来处理
	m_hEvent = 0;

	::CloseHandle(m_hMutex);						// 使用互斥量来处理
	m_hMutex = 0;

	::CloseHandle(m_hSemaphore);					// 使用信号量来处理
	m_hSemaphore = 0;

	CDialogEx::OnClose();
}

// 5.使用信号量来处理
DWORD WINAPI CSellTicketDlg::ThreadProc(LPVOID lpParameter)
{
	CSellTicketDlg* pThis = (CSellTicketDlg*)lpParameter;

	while (pThis->m_bQuitFalg)
	{
		// 5.等待信号量来消息
		// ======================================
		if(::WaitForSingleObject(pThis->m_hSemaphore, 10) == WAIT_TIMEOUT)
			continue;
		// ======================================

		// =============================将这段代码放到上面就可以让产生的负数消失=============================
		if(pThis->m_iAllTicket <= 0)
		{
			// 离开时释放掉信号量
			::ReleaseSemaphore(pThis->m_hSemaphore, 1, 0);
			break;
		}
		// =============================将这段代码放到上面就可以让产生的负数消失=============================

		// =====================给主对话框发消息============================
		::PostThreadMessage(theApp.m_nThreadID, UM_WORK_TO_MAIN, pThis->m_iAllTicket, 0);
		// =====================给主对话框发消息============================

		// 2.10 使用原子访问操作来对变量自加
		::InterlockedDecrement(&(pThis->m_iAllTicket));

		// 离开时释放掉信号量
		// =================
		::ReleaseSemaphore(pThis->m_hSemaphore, 1, 0);
		// =================
	}
	return 0;
}

2.总结

2.1 锁共有5个: 原子访问 、 关键段(临界区) 、 互斥量 、 事件 、 信号量 ;

2.2 5种锁分为两个类型:

1.用户模式: 原子访问 、 关键段(临界区)
2.内核模式: 互斥量 、 事件 、 信号量

2.3 原子访问 、 关键段(临界区) 、 互斥量 这三种锁同一时间只允许一个线程进入

2.4 事件 要么是进去一个线程,要么进去所有线程

2.5 信号量 可以控制进入几个线程

3.生产者消费者模式

3.1 新建一个基于对话框的MFC应用程序

1.在其上放置一个按钮 开始生产 ,两个 List Box ;
2.在构造函数中创建 5 个生产线程, 5 个消费线程;

标签:线程,操作系统,CSellTicketDlg,网络,2019,iAllTicket,cs,pThis,hMutex
来源: https://blog.csdn.net/weixin_42896619/article/details/88430981