南邮Inter多核实验二——Windows环境下多线程程序设计||

来源:互联网 发布:怎样去掉mac上win 编辑:程序博客网 时间:2024/06/06 04:39

 

 

 

 

实 验 报 告

(  2016 / 2017 学年 第 二 学期)

 

 

课程名称

Intel多核程序设计

实验名称

Windows环境下多线程程序设计||

实验时间

2017

5

16

指导单位

计算机学院软件工程系

指导教师

龙显忠

 

 

学生姓名

 

班级学号

 

学院(系)

贝尔英才学院

专    业

理工科强化班(计算机科学与技术)

 

 

实 验 报 告

实验名称

Windows环境下多线程程序设计||

指导教师

龙显忠

实验类型

验证

实验学时

2

实验时间

2017.5.16.

一、      实验目的和要求

实验目的:

1、了解线程库。

2、熟练使用并行程序的线程设计方法。

实验要求:

运行书上例题,验证结果。

 

 

 

 

 

 

二、实验环境(实验设备)

 硬件:计算机

软件:Visual Studio

 

 

 

 

 

 


 

二、      实验原理及内容

实验内容:

(1) 例4.5: Win32线程同步的实现——临界区

假如一个银行系统有两个线程执行取款任务,一个使用存折在柜台取款,一个使用银行卡在ATM取款。若不加控制,很可能账户余额不足两次取款的总额,但还可以把钱取走。

注意:该例子综合运用了全局变量、事件和临界区三种同步机制。

#include "stdafx.h"

#include <windows.h>

#include <process.h>

#include <iostream>

#include <fstream>

using namespace std;

int total = 100 ;//定义全局变量

HANDLE evFin[2] ;//定义句柄数组

CRITICAL_SECTION cs ;//定义临界区变量

void WithdrawThread1(LPVOID param)

{

EnterCriticalSection(&cs) ;//进入临界区,锁定共享资源

  if ( total-90 >= 0)

  {

     total -= 90 ;

     cout<<"You withdraw 90"<<endl;

  }

  else

cout<<"You do not have that much money"<<endl;

LeaveCriticalSection(&cs) ;//退出临界区,释放锁定的资源

SetEvent (evFin[0]) ;//设置evFin[0]为有信号状态

}

void WithdrawThread2(LPVOID param)

{

  EnterCriticalSection(&cs) ;

  if ( total-20 >= 0)

  {

     total -= 20 ;

     cout<<"You withdraw 20"<<endl;

  }

  else

    cout<<"You do not have that much money"<<endl;

  LeaveCriticalSection(&cs) ;

  SetEvent (evFin[1]) ;

}

int main(int argc , char * argv[])

{

evFin[0] = CreateEvent (NULL,FALSE,FALSE,NULL) ;//创建事件

evFin[1] = CreateEvent (NULL,FALSE,FALSE,NULL) ;

InitializeCriticalSection(&cs) ;//初始化临界区对象

_beginthread(WithdrawThread1 , 0 , NULL) ;

     _beginthread(WithdrawThread2 , 0 , NULL) ;

WaitForMultipleObjects(2 ,evFin ,TRUE ,INFINITE) ;

DeleteCriticalSection(&cs) ;

     cout<<total<<endl;

     return 0 ;

}

1、    基本概念

临界区是一种防止多个线程同时执行一个特定代码段的机制

如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开

临界区适用于多个线程操作之间没有先后顺序但要求互斥的同步

多个线程访问同一个临界区的原则:

一次最多只能一个线程停留在临界区内

 不能让一个线程无限地停留在临界区内,否则其它线程将不能进入该临界区

定义临界区变量的方法如下——定义CRITICAL_SECTION类型的对象gCriticalSection

                   CRITICAL_SECTION gCriticalSection;

      通常情况下,CRITICAL_SECTION结构体应该被定义为全局变量,便于进程中的所有线程可以方便地按照变量名来引用该结构体。

2、相关API

初始化临界区——初始化CRITICAL_SECTION类型的对象

VOID WINAPI InitializeCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);

删除临界区——释放CRITICAL_SECTION类型的对象

VOID WINAPI DeleteCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);

进入临界区——允许锁定访问标志

VOID WINAPI EnterCriticalSection(

LPCRITICAL_SECTION lpCriticalSection

);

离开临界区——解除锁定标志

VOID WINAPI LeaveCriticalSection(

   LPCRITICAL_SECTION lpCriticalSection

);

3、使用临界区编程的一般方法:

void WriteData()

{

EnterCriticalSection(&gCriticalSection);//进入临界区,锁定共享资源

//do something  //共享资源的操作

LeaveCriticalSection(&gCriticalSection); //退出临界区,释放锁定

}

运行结果:

当一个线程执行EnterCriticalSection(&cs)这一语句申请进入临界区时,程序会判断cs对象是否已被锁定,如果没有锁定,那么线程就可以进入临界区进行资源访问,同时cs被置为锁定状态;否则,说明已经有线程进入临界区,正在使用共享资源,调用线程将被阻塞以等待cs解锁。所以上面的程序不会出现100元被取走110元的情况。

如果程序不使用临界区技术,在两个线程中去掉EnterCriticalSection

(&cs)和LeaveCriticalSection(&cs)语句,分析发现如果线程1执行了total

-90 >= 0这条语句,发现条件成立,但还没来得及执行total -= 90 ,这时线程2执行total-20 >= 0语句,同样发现条件成立,这样就会出现100元可以被取走110元的情况。

(2)例4.6:Win32线程同步的实现——互斥量

#include "stdafx.h"

#include <windows.h>

#include <iostream.h>

#define THREAD_INSTANCE_NUMBER  3

LONG g_fResourceInUse = FALSE;

LONG g_lCounter = 0;

DWORD ThreadProc(void * pData) {

  int ThreadNumberTemp = (*(int*) pData);

  HANDLE hMutex;

  cout << "ThreadProc: "  << ThreadNumberTemp << " is running!" << endl;

  if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {

     cout << "Open Mutex error!" << endl;

  }

  cout << "ThreadProc " << ThreadNumberTemp << " gets the mutex"<< endl;

  ReleaseMutex(hMutex);

  CloseHandle(hMutex);

  return 0;

}

int main(int argc, char* argv[])

{

  int i;

  DWORD ID[THREAD_INSTANCE_NUMBER];

     HANDLE h[THREAD_INSTANCE_NUMBER];

  HANDLE hMutex;

  if ( (hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {

     if ((hMutex = CreateMutex(NULL, FALSE, "Mutex.Test")) == NULL ) {

        cout << "Create Mutex error!" << endl;

        return 0;

     }

  }

  for (i=0;i<THREAD_INSTANCE_NUMBER;i++)

  {

     h[i] = CreateThread(NULL,

                     0,                         

              (LPTHREAD_START_ROUTINE) ThreadProc,

              (void *)&ID[i],                   

                      0,                      

              &(ID[i]));   

if (h[i] == NULL)

        cout << "CreateThread error" << ID[i] << endl;

     else

        cout << "CreateThread: " << ID[i] << endl;

 

  }

  WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,h,TRUE,INFINITE);

  cout << "Close the Mutex Handle! " << endl;

  CloseHandle(hMutex);

  return 0;

}

1、基本概念

互斥量通常用于协调多个线程或进程的活动,通过“锁定”和“取消锁定”资源,控制对共享资源的访问。

当一个互斥量被一个线程锁定了,其他试图对其加锁的线程就会被阻塞;当对互斥量加锁的线程解除了锁定后,则被阻塞的线程中的一个就会得到互斥量。

锁定互斥量的线程一定也是对其解锁的线程。

互斥量的作用是保证每次只能有一个线程获得互斥量。

2、hMutex = CreateMutex(NULL, FALSE, "Mutex.Test")

使用CreateMutex函数创建:

HANDLE CreateMutex(

   LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性结构指针

   BOOL bInitialOwner, //是否占有该互斥量,TRUE:占有,FALSE:不占有

   LPCTSTR lpName //互斥量的名称

);

返回值:创建成功则返回互斥对象的句柄,否则返回NULL。

CreateMutex作用是找出当前系统是否已经存在指定进程的实例。

如果没有则创建一个互斥量。

3、相关API

CreateMutex  创建一个互斥对象,返回对象句柄;

OpenMutex    打开并返回一个已存在的互斥对象的句柄,使之后续访问;

ReleaseMutex  释放对互斥对象的占用,使之成为可用;

使用互斥量的一般方法是:

void Writedata()

{

WaitForSingleObject(hMutex,…);

...//do something

ReleaseMutex(hMutex);

}

HANDLE OpenMutex(

   DWORD dwDesiredAccess, //MUTEX_ALL_ACCESS 请求对互斥量的完全访问

   BOOL bInheritHandle, //如希望子进程能够继承句柄,则为TRUE

   LPCTSTR lpName //要打开对象的名字

);

OpenMutex函数功能:为现有的一个已命名互斥量对象创建一个新句柄。

运行结果:

(3)例4.7:使用信号量机制同步线程

#include "stdafx.h"

#include <windows.h>

#include <iostream.h>

#define THREAD_INSTANCE_NUMBER  3

DWORD foo(void * pData) {

  int ThreadNumberTemp = (*(int*) pData);

  HANDLE hSemaphore;

  cout << "foo: "  << ThreadNumberTemp << " is running!" << endl;

  if ((hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "Semaphore.Test")) == NULL) {

     cout << "Open Semaphore error!" << endl;

  }

  cout << "foo " << ThreadNumberTemp << " gets the semaphore"<< endl;

  ReleaseSemaphore(hSemaphore, 1, NULL);

  CloseHandle(hSemaphore);

  return 0;

}

int main(int argc, char* argv[])

{

  int i;

  DWORD ThreadID[THREAD_INSTANCE_NUMBER];

     HANDLE hThread[THREAD_INSTANCE_NUMBER];

  HANDLE hSemaphore;

 

  if ((hSemaphore = CreateSemaphore(NULL,0,1, "Semaphore.Test")) == NULL ) {

     cout << "Create Semaphore error!" << endl;

     return 0;

  } 

  for (i=0;i<THREAD_INSTANCE_NUMBER;i++)

  {

     hThread[i] = CreateThread(NULL,

          0,                         

          (LPTHREAD_START_ROUTINE) foo,

          (void *)&ThreadID[i],                   

          0,                      

          &(ThreadID[i]));

     if (hThread[i] == NULL)

        cout << "CreateThread error" << ThreadID[i] << endl;

     else

        cout << "CreateThread: " << ThreadID[i] << endl;

 

  }

  WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,hThread,TRUE,INFINITE);

  cout << "Close the Semaphore Handle! " << endl;

  CloseHandle(hSemaphore);

  return 0;

}

1、基本概念

信号量是一个核心对象,拥有一个计数器,可用来管理大量有限的系统资源。

当计数值大于零时,信号量为有信号状态。

当计数值为零时,信号量处于无信号状态。

2、相关API

创建信号量

HANDLE CreateSemaphore (

        PSECURITY_ATTRIBUTE psa, //安全属性

       LONG lInitialCount,  //设置信号量初始计数,表示开始时可供使用的资源数

       LONG lMaximumCount, //设置信号量最大计数,表示可用的最大资源数

       PCTSTR pszName); //指定信号量对象的名字

释放信号量

BOOL WINAPI ReleaseSemaphore(

         HANDLE hSemaphore,

         LONG lReleaseCount, //信号量的当前资源数增加lReleaseCount

         LPLONG lpPreviousCount

);

打开信号量

   和其他核心对象一样,信号量也可以通过名字进行跨进程访问

HANDLE OpenSemaphore (

       DWORD fdwAccess,

      BOOL bInherithandle,

      PCTSTR pszName

);

3、对信号量同步的另外一种解释——多核架构与编程技术

信号对象是内核对象,它拥有一个计数器,可用来控制多个线程对共享资源进行访问,在创建对象时指定最大可同时访问的线程数。

当一个线程申请访问成功后,信号对象中的计数器减1,调用ReleaseSemaphore()函数后,信号对象中的计数器加1。其中,计数器值大于或等于0,但小于或等于创建时指定的最大值。

如果一个应用在创建一个信号对象时,将其计数器的初始值设为0,就阻塞了其他线程,保护了资源。等初始化完成后,调用ReleaseSemaphore()函数将其计数器的值增加至最大值,则可进行正常的存取访问。

4、信号量的特点和用途

(1)如果当前资源的数量大于0,则信号量有效;

(2)如果当前资源数量是0,则信号量无效;

(3)系统决不允许当前资源的数量为负值;

(4)当前资源数量决不能大于最大资源数量啊啊

运行结果:

(4)例4.11:多线程调试

1、基本概念

众所周知Visual Studio调试器是一个功能非常强大的调试工具,通过它可以观察程序的运行状态并确定逻辑错误的位置。

使用该调试器可以中断(或挂起)程序的执行,以便于检查代码,此外还可以计算和编辑程序中的变量,查看寄存器,查看从源代码创建的指令以及应用程序所占用的内存空间。

使用“编辑并继续”命令,可以在调试时对代码进行更改,然后继续执行。

2、多线程调试方法

设置断点

使用“线程”工具查看当前线程

线程命名

查看局部变量的值

使用监视窗口监视变量的变化情况

还可以使用Intel的线程工具对程序进行调试及优化

VTune Performance Analyzer

运行结果:

 

 

 

 

 

四、实验小结(包括问题和解决方法、心得体会、意见与建议等)

在这次实验中,我学会了Win32线程同步的实现:全局变量、事件(Event)、临界区(Critical section)、互斥量(Mutex)、信号量(Semaphore)这些实现方法。

知道了,线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应该等待,直到消息到达时才被唤醒。

线程互斥是指对于共享资源,在各线程访问时的排它性,即当有多个线程都要使用某一共享资源时,同一时刻却只允许一个线程去使用,而其他要使用该共享资源的线程必须等待,直到占用资源者释放该共享资源。

通过此次实验,我收获很大。

 

 

五、指导教师评语

 

 

 

 

 

 

 

 

 

 

成  绩

 

批阅人

 

日  期

 

 

 

阅读全文
0 0
原创粉丝点击