2007年9月10日星期一

线程的使用

线程的使用
1.创建简单的线程;
在视图类的鼠标的消息映射函数中加入:
    AfxBeginThread((AFX_THREADPROC)TestThread,this);
然后在视图类中加入线程的入口函数:
UINT TestThread(LPVOID pParam)
{
    AfxMessageBox("这是一个测试工作线程的对话框");
    return 0;
}
函数说明:
CWinThread*
AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int
nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD
dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
CWinThread*
AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority =
THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
PfnThreadProc:
    指向工作者线程的控制函数,不能是NULL,函数的定义必须如下:
UINT MyControllingFunction( LPVOID pParam );
PThreadClass:CwinThread的RUNTIME_CLASS对象.
PParam:传递给控制函数的参数.
Npriority:线程的优先级,可以用函数
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);来设定
nStackSize:堆栈的大小,如果为0,就和创建的主线程一样的大小.
DwCreateFlags:创建线程时的附加标志,可以为CREATE_SUSPENDED,代表挂起线程,直到调用DWORD ResumeThread( );函数时才执行,为0,表示立即执行.
LpSecurityAttrs:指向一个SECURITY_ATTRIBUTES描述线程的安全性,如果与主线程一样的安全属性,可以为NULL.
另外可以用CwinThread类的成员函数
BOOL
CreateThread( DWORD dwCreateFlags = 0, UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );来创建一个线程,具体的参数和上面一样.
还可以用API来创建线程:
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);
DWORD WINAPI YourThreadFunc(LPVOIDlpvThreadParm);
lpThreadId:32位标示符,用来接收线程的标示
函数返回的都是创建线程的句柄.
2.如何结束一个线程:
void AfxEndThread( UINT nExitCode );//结束当前正在运行的线程.
NExitCode:为线程退出代码.
BOOL TerminateThread(
  HANDLE hThread,    // handle to the thread
  DWORD dwExitCode   // exit code for the thread
);
如果线程调用了VOID ExitThread(UINTfuExitCode);也可以终止

3.如何取得线程的退出代码;
使用函数
BOOL GetExitCodeThread(
  HANDLE hThread,      // handle to the thread
  LPDWORD lpExitCode   // address to receive termination status
);
4.设置线程的优先级;
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);
nPriority;
线程的优先级,有:THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_TIME_CRITICAL具体的函数参见MSDN。
在上面创建的工作者线程中我们可以这样设置优先级:
    SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);//设置比通常高两个点
5.如何创建多线程多任务。
可以调用利用上面的线程创建函数,传递给同一个线程函数不同的参数,也可以开始另外一个线程。
6.挂起和恢复线程:

前我提到过可以创建挂起状态的线程(通过传递CREATE_SUSPENDED标志给函数CreateThread来实现)。当你这样做时,系统创建指定
线程的核心对象,创建线程的栈,在CONTEXT结构中初始化线程CPU注册成员。然而,线程对象被分配了一个初始挂起计数值1,这表明了系统将不再分配
CPU去执行线程。要开始执行一个线程,一个线程必须调用ResumeThread并传递给它调用CreateThread时返回的线程句柄。
DWORD ResumeThread(HANDLE hThread);
一个线程可以被挂起多次。如果一个线程被挂起3次,则该线程在它被分配CPU之前必须被恢复3次。
除了在创建线程时使用CREATE_SUSPENDED标志,你还可以用SuspendThread函数挂起线程。
DWORDSuspendThread(HANDLE hThread);
7.线程同步对象的使用;

步对象有:Critical_section(临界区),Event(事件),Mutex(互斥对象),Semaphores(信号量)。都有相应的
API创建函数和MFC类,同步对象可以处于两种状态:信号状态(signal
state)或非信号状态,当一个线程与某个对象相关联时,若该对象处于非信号状态,则要等到其变成信号状态线程才能继续执行。
WIN32 API提供了等待命令WaitForSingleObject和WaitForMutipleObjects:
  DWORD WaitForSingleObject(
                HANDLE hObject,                         // 要等待的对象句柄
                DWORD dwMilliseconds);  // 最大等待时间(毫秒)
        DWORD WaitforMultipleObjects(
                DWORD dwNumObjects,             // 要等待的对象数
                LPHANDLE lpHandles,             // 对象句柄数组
                BOOL bWaitAll,               // 是否等待所有对象都有信号才返回
                DWORD dwMilliseconds);  // 最大等待时间

果在指定时间内对象达到 信号状态则返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT,出错返回
WAIT_FAILED。对于互斥量、信号量和自动重置(auto-reset)事件对象,等待成功
时将它们改成非信号状态(信号量计数器减1),以实现对象的互斥访问。
件:jianuangzhiganhue
8.事件:
"事件"――一个允许一个事件发生时线程通知另一个线程的同步对象。在一个线程需要了解何时执行任务时,事件是十分有用的。
CEvent对象有两种类型:自动和手工。一个手工CEvent对象存在于由ResetEvent 或SetEvent设置的状态中,直到另一个函数被调用。
一个自动CEvent对象在至少一个线程被释放后自动返回一个无标记(无用的)状态。
CEvent(
BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName
= NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn;如果为TRUE, CMultilock or CsingleLock对象就可以使用,为FALSE,线程就必须等到直到所需的资源被释放.
BManualReset:为TRUE,设置为手动复位事件对象,为FALSE,设置为自动复位事件对象.
LpszName:事件对象的名称,不能指定一个已经存在的对象
LpsaAttribute:事件对象的安全属性
与同步对象相关的多个同步访问对象CMultilock 和单个同步访问对象 CsingleLock对象.
CSingleLock( CSyncObject* pObject, BOOL bInitialLock = FALSE );
PObject:指向一个要处理的同步对象
BinitialLock:指定是否初始的时候就尝试访问对象。
CMultiLock( CSyncObject* ppObjects[ ], DWORD dwCount, BOOL bInitialLock = FALSE );
DwCount:指定同步对象的个数,必须大于0。
下面简单介绍他们的应用:

用此成员函数构造一个已命名或未命名的CEvent对象。要访问或释放一个CEvent对象,可建立一个CSingleLock或CMultiLock对
象并调用其Lock和Unlock成员函数。要将CEvent对象的状态改为已标记(无须等待的线程),可调用SetEvent或PulseEvent。
要设置一个CEvent对象为无标记(必须等待的线程),可调用ResetEvent。
可以在你的线程开始设置CEvent::SetEvent,其他线程使用CSingleLock::Lock来等待你线程处理结束。当你的线程处理完毕,
使用CEvent::ResetEvent清除事件,其他程序就可以继续执行了。
API函数:
    HANDLE CreateEvent(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针,Win98忽略
                BOOL bManualRest,              // 是否手动重置(manual-reset)
                BOOL bInitialState,            // 初始状态是否为信号状态
                LPCTSTR lpName);              // 对象名字符串指针

动重置(auto-reset)事件和手动重置(manual-reset)事件,这由CreateEvent()的第二个参数指定。对于自动重置事件,
WaitForSingleObject()和
WaitForMultipleObjects()会等待事件到信号状态,随后又自动将其重置为非信号状
态,这样保证了等待此事件的线程中只有一个会被唤醒。而手动重置事件需要用户调用ResetEvent()才会重置事件。可能有若干个线程在等待同一事
件,这样当事件变 为信号状态时,所有等待线程都可以运行了。
SetEvent()函数用来把事件对象设置成信号状态,ResetEvent()把事件对象重置成非信号状态,两者均需事件对象句柄作参数。
9.临界区

一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。在运行性能比较重要而且资源不会跨进程使用
时,建议采用临界区代替信号灯。有关在MFC中使用信号灯的详细信息,请参阅CMutex。使用CCriticalSection对象之前,需要构造它。
在构造函数返回后,就可以使用临界区了。在使用完之后要调用UnLock函数。存取由CCriticalSection控制的资源时,要在资源的存取函数
中定义一个CSingleLock型的变量。然后调用加锁对象的Lock成员函数(如CSingleLock::Lock)。此时,调用的线程要么获得对
资源的存取权,要么等待他人释放资源等待加锁,或者等待他人释放资源,但又因为超时而加锁失败。这样就保证了一次只有一个线程在存取临界资源。释放资源只
需调用成员函数UnLock(例如CSingleLock:Unlock),或让锁对象在作用范围之外。
此外,可以单独地建立一个CCriticalSection对象,并在存取临界资源之前显式地存取它。这种方式有助于保持代码的清晰,
但是更容易出错,因为要记住在存取临界资源前加锁,存取之后开锁。
构造函数:CCriticalSection ( );
成员函数:BOOL Lock( ); BOOL Lock( DWORD dwTimeout ); 本函数用于取得对临界区对象的存取权。Lock是一个成块性的操作,直到取得临界区对象的存取权才返回。
virtual
BOOL UnLock( );
如果临界区是被本线程占用的并且开锁成功,则返回非零值。否则为0。本函数释放线程占用的CCriticalSection对象。如果
CCriticalSection是单独使用的,在独占使用完临界资源以后,应尽快调用UnLock开锁。如果使用了CSingleLock对象,
CCriticalSection::Unlock将会在它的成员函数Unlock中调用。
API函数:
CRITICAL_SECTION cs; 然后初始化,调用下面函数(参数为空对象的指针):
     VOID InitializeCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID EnterCriticalSection(LPCRITICAL_SECTION lpcs); 进入和离开临界区分别调用函数:
     VOID LeaveCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID DeleteCriticalSection(LPCRITICAL_SECTION lpcs); 删除临界区对象用:
10. Mutex(互斥对象)
它为一个同步对象,允许某线程共同访问同一资源。在仅仅一个线程被允许用于修改数据或其它被控制的资源时。

种使用CMutex
对象的方法就是一个CMutex类型的变量,将其作为你希望控制类的数据成员。在被控制对象的构造过程中,若互斥对象最初拥有了互斥对象的名称或期待的安
全属性,那么就调用CMutex数据成员指定的构造函数,以这种方式访问由CMutex
对象控制的资源,首先要在资源访问的成员函数中创建CSingleLock类型或CMultiLock类型的变量。然后调用封锁对象的Lock成员函数
(例如,
CSingleLock::Lock)。这样,你的线程要么就获得资源的访问权,以等待将要释放的资源,并获取访问权,要么就等待将要释放的资源,当超时
后,返回失败。在任何一种情况下,都可以在线程安全的模式下访问资源。若要释放这些资源,使用封锁对象的Unlock成员函数(例如,
CSingleLock::Unlock),或允许封锁对象越界。
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL,LPSECUR-ITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn:指定是否创建了CMutex 对象的线程最初拥有由互斥对象控制资源的访问权。
lpszName:
CMutex
对象的名称。若其它的互斥对象由同样的名称,那么必须提供lpszName,以便对象能够通过进程边界。若该值为NULL,那么互斥对象将没有命名。若该
名称匹配已经存在的互斥对象,那么构造函数将创建一个参考互斥对象名称的新CMutex
对象。若该名称匹配已存在的不是互斥对象的同步对象,那么构造过程将失败。
LpsaAttribute:对象的安全性
API函数:

HANDLE CreateMutex(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针
                BOOL bInitialOwner,             // TR UE表示线程将拥有该信号量
                LPCTSTR lpName);                // 对象名

别的进程中可以用相同的互斥量名调用CreateMutex()和
OpenMutex()来打开该对象,这样互斥量就可用于多个进程中,线程通过WaitForSingleObject()和
WaitForMultipleObject()来等待一个互斥量,用ReleaseMutex()来释放一个互斥量。
11.Semaphores(信号量)
    一个CSemaphore类对象代表一个"信号"一个同步对象,它允许有限数目的线程在一个或多个进程中访问同一个资源。
一个Semaphore对象保持了对当前访问某一指定资源的线程的计数。
对于一个只能支持有限数目用户的共享资源来说,CSemaphore是很有用的。
CSemaphore
对象的当前计数是还可以允许的其它用户的数目。当这个计数达到零的时候,所有对这个由CSemaphore对象控制的资源的访问尝试都将被插入到一个系统
队列中等待,直到它们的时间用完或计数值不再为零。这个被控制的资源可以同时接受访问的最大用户数目是在CSemaphore对象的构造期间被指定的。
使
用CSemaphore对象的另一种方法是,将一个CSemaphore类型的变量添加到你想要控制的类中作为一个数据成员。在被控制对象的构造期间,调
用CSemaphore数据成员的构造函数来指定访问计数的初始值,访问计数的最大值,信号的名字(如果它要在整个进程中使用),以及需要的安全标志。要
访问由CSemaphore对象用这种方式控制的资源,首先要在你的资源的访问成员函数中创建一个CSingleLock类型或CMultiLock类型
的变量。然后调用加锁对象的Lock成员函数(例如,CSingleLock::Lock)。这时,你的线程将达到对资源的访问,等待资源被释放并访问
它,或者是在等待资源被释放的过程中超过了时间,对资源的访问失败。不管是哪一种情况,你的资源都是以一种线程安全(thread-safe)方式被访问
的。要释放资源,可以使用加锁对象的Unlock成员函数(例如,CSingleLock::Unlock),或者是让加锁对象超越范围。
CSemaphore(
LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName=
NULL,       LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
LInitialCount:信号的初始使用计数。必须是大于或等于0,并且小于或等于lMaxCount。
LMaxCount:信号的使用计数的最大值。必须大于0。
PstrName:
信号的名字。如果此信号将在整个进程中被访问,则必须提供这个名字。如果是NULL,则对象将是没有名字的。如果这个名字与一个已经存在的信号的名字一
样,则构造函数创建一个新的CSemaphore对象,此对象引用具有这个名字的对象。如果这个名字与一个已经存在的但不是一个信号的同步对象的名字一
样,则构造函数会失败。
API使用:
HANDLE CreateSemaphore(
                LPSECURITY_ATTRIBUTES lpSecAttr,        // 安全属性指针
                LONG InitialCoutn,                       // 初始信号量数目
                LONG lMaxCount,                        // 允许的最大信号量数目
                LPCTSTR lpszSemName);                 // 信号量对象名指针

信号量数目大于0时,就处于信号状态。若lpszSemName参数为NULL时,信号量局限于一个进程的线程中;若给予一个字符串对象名,则其它进程也
可以使
用该信号量。别的进程中可以在调用CreateSemaphore()和OpenSemaphore()时把这个字符串名作参数,以打开该信号量,这样可
以实现多个进程间的同步。WaitForSingleObject()和WaitForMultipleObjects()来等待一个信号量。当完成同步
任务时,线程应用ReleaseSemaphore()来释放信号量:
BOOL ReleaseSemaphore(HANDLE hSema, LONG lReleaseCount, LPLONG  lpPrevious);
12.用户界面线程的创建:
创建用户界面线程时,必须首先从 CWinThread 派生类。必须使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREAT宏声明并实现此类。
用户界面线程通常用于处理用户输入和响应用户事件。
13.进程的创建和终止;
调用CreateProcess函数创建新的进程,运行指定的程序。CreateProcess的原型如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
其中:
lpApplicationName指向包含了要运行模块名字的字符串。
lpCommandLine指向命令行字符串。
lpProcessAttributes描述进程的安全性属性,NT下有用。
lpThreadAttributes描述进程初始线程(主线程)的安全性属性,NT下有用。
bInHeritHandles
表示子进程(被创建的进程)是否可以继承父进程的句柄。可以继承的句柄有线程句柄、有名或无名管道、互斥对象、事件、信号量、映像文件、普通文件和通讯端
口等;还有一些句柄不能被继承,如内存句柄、DLL实例句柄、GDI句柄、URER句柄等等。
子进程继承的句柄由父进程通过命令行方式或者进程间通讯(IPC)方式由父进程传递给它。
dwCreationFlags表示创建进程的优先级类别和进程的类型。创建进程的类型分控制台进程、调试进程等;
优先级类别用来控制进程的优先级别,分Idle、Normal、High、Real_time四个类别。
lpEnviroment指向环境变量块,环境变量可以被子进程继承。
lpCurrentDirectory指向表示当前目录的字符串,当前目录可以继承。
lpStartupInfo指向StartupInfo结构,控制进程的主窗口的出现方式。
lpProcessInformation指向PROCESS_INFORMATION结构,用来存储返回的进程信息。
从其参数可以看出创建一个新的进程需要指定什么信息。
从上面的解释可以看出,一个进程包含了很多信息。若进程创建成功的话,返回一个进程信息结构类型的指针。进程信息结构如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
进程信息结构包括进程句柄,主线程句柄,进程ID,主线程ID。
进程的终止
进程在以下情况下终止:
调用ExitProcess结束进程;
进程的主线程返回,隐含地调用ExitProcess导致进程结束;
进程的最后一个线程终止;
调用TerminateProcess终止进程。
当要结束一个进程时,发送WM_QUIT消息给主窗口,当然也可以从它的任一线程调用ExitProcess。

9.边界 测试
在测试用例部分已经说过,设计测试用例时要考虑边界输入和非法输入,这里统称为特殊输入,程序员在编写代码时也要考虑特殊输入,要为特殊输入编写处理代
码。在实际工作中,程序员没有考虑到某些特殊输入是很常见的,这也是程序错误的一个重要来源。不幸的是,如果编写代码和建立测试用例时都没有考虑这些输
入,那么白盒覆盖也不能自动发现,因为白盒覆盖是以代码为基础的,如果相应的代码根本不存在,白盒覆盖当然不会告诉用户"某某代码未覆盖"。

  特殊输入通常与数据类型有关,例如,如果一个参数是指针,空指针就是一个特殊输入,对于一个整数类型,最大值、最小值、0、1、-1都可以算是特殊输
入;另一方面,当输入特殊数据时,如果程序未作合适的处理,运行结果常常是产生异常。根据这两个条件,如果预先为各个数据类型定义特殊值,然后由测试工具
自动生成测试用例,使用这些特殊值或其组合作为输入数据进行测试,通常可以发现"未处理特殊输入"而产生的程序错误,这就是边界测试。
 

没有评论: