第一篇:操作系统-创建线程,利用互斥实现线程共享变量通信介绍
2016-2017学年度第一学期大作业
课程名称:
操作系统(含课程设计)
任课教师:
解
晓
萌
作业题目:
创建线程,利用互斥实现线程共享变量通信
姓
名:
鲁
斌
学
号:
20***0
2专
业:
计算机科学与技术
教学中心:
宝 安 学 文
联系电话:
***
评审日期__________成绩_________评审教师(签名)__________
华南理工大学网络教育学院
“计算机操作系统”课程设计大作业
实验报告
一、题目: 创建线程,利用互斥实现线程共享变量通信
二、目的
掌握线程创建和终止,加深对线程和进程概念的理解,会用同步与互斥方法实现线程之间的通信。
三、概述:
为了确保读线程读取到的是经过修改的变量,必须在向变量写入数据时禁止其他线程对它的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。这保证了线程了解其他线程任务处理结束后的结果而采取的保护措施称为线程同步。
从大的方面来讲,线程的同步分为用户模式的线程同步和内核对象的线程同步。用户模式中线程的同步方法有原子访问和临界区等方法。它的特点是同步速度特别快,适合对线程运行速度有严格要求的场合。
内核对象的线程同步由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这同步机制使用了内核对象,使用时一定要将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,所以同步速度较慢,但适用性却要远优于用户模式的线程 临界区
临界区是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问若有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可继续抢占,并以此达到用原子方式操作共享资源的目的。
使用临界区保持线程同步
以下是通过一段代码说明了临界区在保护多线程访问共享资源中的作用。通过两个线程分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对它进行初始化。为了使实验效果更
明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,令其他线程同它抢占CPU的可能性加大。若不用临界区对它进行保护,则共享资源数据将被破坏,但使用临界区对线程保持同步后就可得到正确的结果。代码如下:
// 临界区结构对象 CRITICAL_SECTION g_cs;// 共享资源 char g_cArray[10];UINT ThreadProc10(LPVOID pParam){ // 进入临界区
EnterCriticalSection(&g_cs);// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[i] = 'a';
Sleep(1);} // 离开临界区
LeaveCriticalSection(&g_cs);return 0;} UINT ThreadProc11(LPVOID pParam){ // 进入临界区
EnterCriticalSection(&g_cs);// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[101] = 'b';
Sleep(1);} // 离开临界区
LeaveCriticalSection(&g_cs);return 0;} // 临界区结构对象 CRITICAL_SECTION g_cs;// 共享资源 char g_cArray[10];UINT ThreadProc10(LPVOID pParam){ // 进入临界区
EnterCriticalSection(&g_cs);// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[i] = 'a';
Sleep(1);} // 离开临界区
LeaveCriticalSection(&g_cs);return 0;} UINT ThreadProc11(LPVOID pParam){ // 进入临界区
EnterCriticalSection(&g_cs);
// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[101] = 'b';
Sleep(1);} // 离开临界区
LeaveCriticalSection(&g_cs);return 0;} ……
void CSample08View::OnCriticalSection(){ // 初始化临界区
InitializeCriticalSection(&g_cs);// 启动线程
AfxBeginThread(ThreadProc10, NULL);AfxBeginThread(ThreadProc11, NULL);// 等待计算完毕
Sleep(300);// 报告计算结果
CString sResult = CString(g_cArray);AfxMessageBox(sResult);}
信号量内核对象
信号量内核,允许多个线程在同一时刻访问同一资源,但需限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时应要同时指出允许的最大资源计数和当前可用资源计数。一般将当前可用资源计数设
为最大资源计数,每增一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0的,就可发出信号量信号。但当前可用计数减小到0时则说明当前占用资源的线程数已达到了所允许的最大数目,不能在允许其他线程的进入,这时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不能大于最大资源计数。
使用信号量内核对象进行线程同步主要会用到OpenSemaphore()、ReleaseSemaphore()、CreateSemaphore()、WaitForMultipleObjects()和WaitForSingleObject()等函数。其中,CreateSemaphore()是用来创建一个信号量内核对象,其函数原型为:
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
LONG lInitialCount, // 初始计数
LONG lMaximumCount, // 最大计数
LPCTSTR lpName // 对象名指针);参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不超过4294967295。lpName参数可为创建的信号量定义一个名字,由于其创建的是一个内核对象,所以在其他进程中可通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:
HANDLE OpenSemaphore(DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 信号量名);在线程离开对共享资源的处理时,一定要通过ReleaseSemaphore()来增加当前可用资源计数。否则将会导致当前正在处理共享资源的实际线程数并不能达到要限制的数值,而其他线程却因为当前可用资源计数为0但仍无法进入的情况。ReleaseSemaphore()的函数原型为:
BOOL ReleaseSemaphore(HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 计数递增数量
LPLONG lpPreviousCount // 先前计数);互斥是用途广泛的内核对象。能保证多个线程对同一共享资源的互斥访问。只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,所以就决定了无论在什么情况下,这共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,便于其他线程在获得后得以访问资源。互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许它进行一些其他内核对象不能进行的非常规操作。为了便于理解,可参照互斥内核
用互斥内核对象来保持线程同步可能用到的函数有OpenMutex()、ReleaseMutex()、CreateMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。
在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:
// 互斥对象
HANDLE hMutex = NULL;har g_cArray[10];UINT ThreadProc18(LPVOID pParam){ // 等待互斥对象通知
WaitForSingleObject(hMutex, INFINITE);// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[i] = 'a';
Sleep(1);} // 释放互斥对象
ReleaseMutex(hMutex);return 0;} UINT ThreadProc19(LPVOID pParam){ // 等待互斥对象通知
WaitForSingleObject(hMutex, INFINITE);// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[101] = 'b';
Sleep(1);} // 释放互斥对象
ReleaseMutex(hMutex);return 0;} ……
void CSample08View::OnMutex(){ // 创建互斥对象
hMutex = CreateMutex(NULL, FALSE, NULL);// 启动线程
AfxBeginThread(ThreadProc18, NULL);AfxBeginThread(ThreadProc19, NULL);// 等待计算完毕
Sleep(300);// 报告计算结果
CString sResult = CString(g_cArray);AfxMessageBox(sResult);}// MFC互斥类对象
CMutex g_clsMutex(FALSE, NULL);UINT ThreadProc27(LPVOID pParam){ // 等待互斥对象通知
g_clsMutex.Lock();// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[i] = 'a';
Sleep(1);} // 释放互斥对象
g_clsMutex.Unlock();return 0;} UINT ThreadProc28(LPVOID pParam){ // 等待互斥对象通知
g_clsMutex.Lock();// 对共享资源进行写入操作
for(int i = 0;i < 10;i++){
g_cArray[101] = 'b';
Sleep(1);
} // 释放互斥对象
g_clsMutex.Unlock();return 0;} ……
void CSample08View::OnMutexMfc(){ // 启动线程
AfxBeginThread(ThreadProc27, NULL);AfxBeginThread(ThreadProc28, NULL);// 等待计算完毕
Sleep(300);// 报告计算结果
CString sResult = CString(g_cArray);AfxMessageBox(sResult);} 设计体会
1、线程能更好地提高程序的并行执行程度,充分地发挥多处理机的优越性。
2、线程减少了程序并发执行时所付出的时空开销,使OS具有更好的并发性。
3、线程的使用使程序处理更快更灵活,而这灵活同样也带来各种不确定性的可能。特别是在多个线程对同一公共变量进行访问时,虽然未使用线程同步的程序代码在逻辑上可能没有什么问题,但为了确保程序的正确、可靠运行,一定要在适当的场合采取线程同步措施。
4.线程有效地提高系统资源的利用率和系统的吞吐量。