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

来源:互联网 发布:qq三国js心法 编辑:程序博客网 时间:2024/06/06 16:58

 

 

 

 

实 验 报 告

(  2016 / 2017 学年 第 二 学期)

 

 

课程名称

Intel多核程序设计

实验名称

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

实验时间

2017

5

9

指导单位

计算机学院软件工程系

指导教师

龙显忠

 

 

学生姓名

 

班级学号

 

学院(系)

贝尔英才学院

专    业

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

 

 

实 验 报 告

实验名称

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

指导教师

龙显忠

实验类型

验证

实验学时

2

实验时间

2017.5.9.

一、      实验目的和要求

实验目的:

1、了解线程库。

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

实验要求:

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

 

 

 

 

 

 

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

 硬件:计算机

软件:Visual Studio

 

 

 

 

 

 


 

三、实验原理及内容

实验内容:

(1) 例4.1:使用_beginthread创建线程的例子

#include "stdafx.h"

#include <windows.h>

#include <process.h>

#include <iostream>

#include <fstream>

using namespace std;

void ThreadFunc1(PVOID param)

{

while(1)

{

    Sleep(1000);

cout<<"This is ThreadFunc1"<<endl;

}

}

void ThreadFunc2(PVOID param)

{

while(1)

{

    Sleep(1000);

cout<<"This is ThreadFunc2"<<endl;

}

}

int main()

{

int i=0;

_beginthread(ThreadFunc1,0,NULL);

_beginthread(ThreadFunc2,0,NULL);

         Sleep(3000);

         cout<<"end"<<endl;

         return 0;

}

1、    _beginthread(ThreadFunc1,0,NULL);

创建线程还可以用process.h头文件中声明的C执行时期链接库函数_beginthread。它的语法如下:

    hThread = _beginthread (void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist) ;

如果创建线程成功,函数将返回一个新线程的句柄,如果失败将返回-1,其中参数的含义如下:

start_address: 新线程的起始地址,指向新线程调用的函数的起始地址;

stack_size: 新线程的堆栈大小,可以为0;

arglist: 传递给线程的参数列表,无参数时为NULL。

运行结果:

(2) 例4.2: Win32多线程的实现

下面这个程序首先创建两个线程,当输入为1时,执行线程,否则挂起线程。

#include <windows.h>

#include <iostream>

using namespace std;

 

DWORD WINAPI FunOne(LPVOID param){

  while(true)

  {

    Sleep(1000);

    cout<<"hello! ";

  }

return 0;

}

DWORD WINAPI FunTwo(LPVOID param){

  while(true)

  {

    Sleep(1000);

    cout<<"world! ";

  }

  return 0;

}

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

{

int input=0;

  HANDLE hand1=CreateThread (NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);

  HANDLE hand2=CreateThread (NULL, 0, FunTwo, (void*)&input, CREATE_SUSPENDED, NULL);

  while(true){

    cin>>input;

    if(input==1)

    { 

ResumeThread(hand1);

        ResumeThread(hand2);

    }

    else

    {  

SuspendThread(hand1);

        SuspendThread(hand2);

    }

  };

  TerminateThread(hand1,1);

  TerminateThread(hand2,1);

  return 0;

}

1、DWORD WINAPI FunOne(LPVOID param);

Win32 函数库中提供了操作多线程的函数, 包括创建线程、管理线程、终止线程、线程同步等接口。

线程必须从一个指定的函数开始执行,称为“线程函数”,具有如下原型:

DWORD WINAPI ThreadFunc (LPVOID lpvThreadParm);

其中,DWORD WINAPI是一个返回DWORD(32位数据)的API函数,LPVOID可以理解成long型的指针,指向void型,也就是说该函数的输入参数是一个LPVOID型的参数,返回一个DWORD型的值。

通过上面这种定义方式,可以定义一个线程函数,还可以将一个线程的运行入口指向这个线程函数。

2、HANDLE hand1=CreateThread (NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);

所有的进程开始时都是只有一个线程,这个线程就是主线程,可以用微软提供的API来创建更多新的线程。

HANDLE CreateThread (

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize, 

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,  

DWORD dwCreationFlags,

LPDWORD lpThreadId  

);

其中,HANDLE称为句柄,是Windows操作系统中的一个概念,在Windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标示这些资源的标号,即句柄,是一个32位的无符号整数。

第一个参数lpThreadAttributes:一个LPSECURITY_ ATTRIBUTES结构的安全属性指针,该结构指定了要创建的线程的安全属性,默认值为 NULL。

第二个参数dwStackSize:线程堆栈的大小,一般设置为0。

第三个参数lpStartAddress:新线程开始执行时,线程函数的入口地址。它必须是将要被新线程执行的函数地址,不能为NULL。

第四个参数lpParameter:是线程函数定义的参数。可以通过这个参数传送值,包括指针或者NULL 。

第五个参数dwCreationFlags:控制线程创建的附加标志,可以设置两种值。如果该参数为0,则表示线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;

第六个参数lpThreadId:新线程的ID号将会传到这里。

返回值:如果CreateThread()创建成功,将传回一个HNADLE,表示新线程,否则返回NULL;如果失败,则可以通过调用GetLastError()来获取失败原因。

3、ResumeThread(hand1); SuspendThread(hand1);

进程中的每个线程都有挂起计数器(suspend count) ,挂起计数器可以认为是用来记录被挂起的线程的数量。

当挂起计数器值为0 时,线程被执行。

当挂起计数器值大于0 时,调度器不去调度该线程。

不能够直接访问线程的挂起计数器,但可以通过调用Windows API函数来改变它的值。 也可以通过调用SuspendThread()函数来挂起;还可以通过调用ResumeThread()函数来恢复。

原型

  DWORD SuspendThread(HANDLE hThread);

  挂起指定的线程

  如果函数执行成功,则线程的执行被终止

  每次调用SuspendThread() 函数,线程将挂起计数器的值增1。

  DWORD ResumeThread(HANDLE hThread);

  结束线程的挂起状态来执行这个线程

  每次调用ResumeThread() 函数,线程将挂起计数器的值减1

  若挂起计数器的值为0,则不会再减。啊

4、TerminateThread(hand1,1);

在线程函数返回时,线程自动终止。

如果需要在线程的执行过程中终止则可调用函数:

  VOID ExitThread (DWORD dwExitCode) ;

      其中, 参数dwExitCode用来设置线程的退出码。

如果在线程的外面终止线程,则可调用下面的函数:

  BOOL TerminateThread (

                   HANDLE hThread,

                   DWORD dwExitCode) ;

其中,各参数的含义如下:

      hThread:将被终结的线程的句柄;

      dwExitCode:线程的退出码;

使用TerminateThread()函数终止某个线程的执行是不安全的,可能会引起系统不稳定,虽然该函数立即终止线程的执行,但并不释放线程所占用的资源,

因此,一般不建议使用。

运行结果:

DWORD WINAPI FunTwo(LPVOID param){

  while(true)

  {

     Sleep(1000); //改为Sleep(900);

     cout<<"world!";

  }

  return 0;

}

 

 

(3)例4.3: Win32线程同步的实现——全局变量

用全局变量同步线程的例子——使用了全局变量globalvar和while循环达到线程间的同步 ,即线程ThreadFunc和主线程之间的同步。

#include <windows.h>

#include <iostream>

using namespace std;

int globalvar = false;

DWORD WINAPI ThreadFunc(LPVOID pParam)

{

  cout<<"ThreadFunc"<<endl;

  Sleep(200);

  globalvar = true;

  return 0;

}

int main()

{

HANDLE hthread =CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);

  if (!hthread)

  {

cout<<"Thread Create Error ! "<<endl;

CloseHandle(hthread);

  }

  while (!globalvar)

        cout<<"Thread while"<<endl;

  cout<<"Thread exit"<<endl;

  return 0;

     }

1、基本概念

线程之间通信的两个基本问题是互斥和同步。

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

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

线程互斥是一种特殊的线程同步

Win32线程同步的实现:

全局变量

事件(Event)

临界区(Critical section)

互斥量(Mutex)

信号量(Semaphore)

2、全局变量的原理

进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。例如:

int var; //全局变量

UINT ThreadFunction(LPVOID pParam)

{

  while (var)

  {

        //线程处理

  }

  return 0;

}

3、存在的问题

在上面的程序中,var是一个全局变量,任何线程均可以访问和修改它。线程间可以利用此特性达到线程同步的目的。

主线程等待globalvar为真,如不为真则一直循环,这样占用了CPU资源。

如果主线程优先级高于ThreadFunc,则globalvar一直不会被置为真。

CloseHandle()关闭一个线程句柄对象,表示不再使用该句柄,即不对这个句柄对应的线程做任何干预了。

运行结果:

 

 

 

 

 

 

 

 

(4)例4.4:Win32线程同步的实现——事件

有三个线程:主线程、读线程ReadThread、写线程WriteThread。

读线程ReadThread必须在写线程WriteThread 的写操作完成之后才能进行读操作。

主线程必须在读线程ReadThread 的读操作完成后才结束。

定义两个事件对象evRead,evFinish。

evRead由写线程WriteThread用于通知读线程ReadThread 进行读操作。

evFinish由读线程ReadThread用于通知主线程读操作已经结束。

#include "stdafx.h"

#include <windows.h>

#include <process.h>

#include <iostream>

#include <fstream>

using namespace std;

HANDLE evRead, evFinish;

void ReadThread(LPVOID param)

{

WaitForSingleObject (evRead ,INFINITE);

  cout<<"Reading"<<endl;

  SetEvent (evFinish);

}

void WriteThread(LPVOID param)

{

  cout<<"Writing"<<endl;

  SetEvent (evRead);

}

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

{

  evRead = CreateEvent (NULL ,FALSE ,FALSE ,NULL) ;

  evFinish = CreateEvent (NULL ,FALSE ,FALSE ,NULL) ;

  _beginthread(ReadThread , 0 , NULL) ;

  _beginthread(WriteThread , 0 , NULL) ;

  WaitForSingleObject (evFinish,INFINITE) ;

  cout<<"The Program is End"<<endl;

  return 0 ;

}

1、基本概念

事件(Event)是WIN32提供的最灵活的线程间同步方式。

事件存在两种状态:

激发状态(signaled or true)或称为有信号状态。

未激发状态(unsignal or false)或称为无信号状态。

事件可分为两类:

手动设置:这种对象只能用程序来手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。

自动恢复:一旦事件发生并被处理后,将自动恢复到没有事件状态,因此不需要再次设置。

2、evRead = CreateEvent (NULL ,FALSE ,FALSE ,NULL);

创建事件的函数原型为:

HANDLE CreateEvent(

      LPSECURITY_ATTRIBUTES lpEventAttributes,

      BOOL bManualReset,

      BOOL bInitialState,

      LPCTSTR lpName

);

其中:

  第一个参数:与CreateThread中的第一个参数类似,是一个指向LPSECURITY_ATTRIBUTES结构的指针

  第二个参数:代表事件的类型,是手动清除事件信号还是自动清除事件信号。TRUE为手动清除。

  第三个参数:指明事件的初始状态

  第四个参数:事件的名称

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

事件对象创建函数中,如果第二个参数是手工重置事件(TRUE),那么WaitForSingleObject()函数的调用不会影响它的状态,它将总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件。如果是自动重置事件,那么它的状态在WaitForSingleObject()函数调用后,会自动变为无信号状态。

3、WaitForSingleObject (evFinish,INFINITE);

Win32 API提供了一组能使线程阻塞其自身执行的等待函数:

WaitForSingleObject ()函数

WaitForMultipleObjects ()函数

原型

   DWORD WaitForSingleObject(

      HANDLE hHandle,

      DWORD dwMilliseconds);

其中,

hHandle:需要等待的线程的句柄; dwMilliseconds:需要等待的时间上限, 单位为毫秒。

WaitForSingleObject ()调用后,只有满足下面两种情况才会返回,这两种情况如下:

a)  等待上限时间,即函数等待时间超过设定的时间上限dwMilliseconds时,函数将返回,因此也可以设定dwMilliseconds为INFINITE,即无时间上限。函数在不满足第二种情况下将一直等待。

b)  等待对象处于有信号状态,即线程句柄hHandle处于有信号状态。线程在执行过程中,它所对应的句柄会是无信号状态的,只有当线程运行完毕返回时,线程对象句柄才是有信号状态。

4、使用“事件”机制注意事项

a)  设置事件是否要自动恢复;

b)  设置事件的初始状态;

c)  如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突

事件对象属于内核对象,进程A可以通过调用OpenEvent函数根据对象的名字获得进程B中Event对象的句柄,然后对这个句柄可以使用ResetEvent、SetEvent和WaitForMultipleObjects等函数进行操作来实现一个进程的线程控制另一进程中线程的运行 。例如:

HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS, true, “MyEvent”);

ResetEvent(hEvent);

   其中,EVENT_ALL_ACCESS表示对事件MyEvent的所有访问权限,true表示返回的句柄hEvent继承了事件MyEvent, MyEvent是将要打开的事件对象的名字。OpenEvent的功能是打开一个已经存在的命名事件对象。

当程序中一个线程的运行要等待另外一个线程中一项特定的操作的完成才能继续执行时,就可以使用事件对象来通知等待线程某个条件已满足。

运行结果:

虽然读线程比写线程先创建,但是它要等待写线程复位读事件对象后,才能够继续执行。同样主线程必须等待读线程复位结束事件对象后,才能继续执行并结束程序。

 

 

 

 

 

 

 

 

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

在这次实验中,我对创建线程、管理线程、终止线程、线程同步有了深入的学习和应用。

线程必须从一个指定的函数开始执行,称为“线程函数”, DWORD WINAPI ThreadFunc (LPVOID lpvThreadParm)。

所有的进程开始时都是只有一个线程,这个线程就是主线程,可以用微软提供的API来创建更多新的线程,CreateThread或者_beginthread。

调用SuspendThread()函数进行线程的挂起,调用ResumeThread()函数进行线程的恢复。

Win32 API提供了一组能使线程阻塞其自身执行的等待函数WaitForSingleObject ()。

在线程函数返回时,线程自动终止ExitThread和TerminateThread 。

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

五、指导教师评语

 

 

 

 

 

 

成  绩

 

批阅人

 

日  期

 

 


阅读全文
0 0