MFC的进程与线程

http://www.shineblog.com/user1/3092/archives/2005/29691.shtml

 

Win32的进程和线程概念

进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其他操作系统资源(如进程创建的文件、管道、同步对象等)组成。一个应用程序可以有一个或多个进程,一个进程可以有一个或多个线程,其中一个是主线程。

线程是操作系统分时调度分配 CPU时间的基本实体。一个线程可以执行程序的任意部分的代码,即使这部分代码被另一个线程并发地执行;一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。

之所以有线程这个概念,是因为以线程而不是进程为调度对象效率更高:

由于创建新进程必须加载代码,而线程要执行的代码已经被映射到进程的地址空间,所以创建、执行线程的速度比进程更快。

一个进程的所有线程共享进程的地址空间和全局变量,所以简化了线程之间的通讯。

Win32的进程处理简介

因为 MFC没有提供类处理进程,所以直接使用了Win32 API函数。

进程的创建

调用 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 表示创建进程的优先级类别和进程的类型。创建进程的类型分控制台进程、调试进程等;优先级类别用来控制进程的优先级别,分IdleNormalHighReal_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终止进程。

当要结束一个 GDI进程时,发送WM_QUIT消息给主窗口,当然也可以从它的任一线程调用ExitProcess

Win32的线程

线程的创建

使用 CreateThread函数创建线程,CreateThread的原型如下:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,

DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags, // creation flags

LPDWORD lpThreadId

);

其中:

lpThreadAttributes 表示创建线程的安全属性,NT下有用。

dwStackSize 指定线程栈的尺寸,如果为0则与进程主线程栈相同。

lpStartAddress 指定线程开始运行的地址。

lpParameter 表示传递给线程的32位的参数。

dwCreateFlages 表示是否创建后挂起线程(取值CREATE_SUSPEND),挂起后调用ResumeThread继续执行。

lpThreadId 用来存放返回的线程ID

线程的优先级别

进程的每个优先级类包含了五个线程的优先级水平。在进程的优先级类确定之后,可以改变线程的优先级水平。用 SetPriorityClass设置进程优先级类,用SetThreadPriority设置线程优先级水平。

Normal 级的线程可以被除了Idle级以外的任意线程抢占。

线程的终止

以下情况终止一个线程:

调用了 ExitThread函数;

线程函数返回:主线程返回导致 ExitProcess被调用,其他线程返回导致ExitThread被调用;

调用 ExitProcess导致进程的所有线程终止;

调用 TerminateThread终止一个线程;

调用 TerminateProcess终止一个进程时,导致其所有线程的终止。

当用TerminateProcess或者TerminateThread终止进程或线程时,DLL的入口函数DllMain不会被执行(如果有DLL的话)。

线程局部存储

如果希望每个线程都可以有线程局部 (Thread local)的静态存储数据,可以使用TLS线程局部存储技术。TLS为进程分配一个TLS索引,进程的每个线程通过这个索引存取自己的数据变量的拷贝。

TLS DLL是非常有用的。当一个新的进程使用DLL时,在DLL入口函数DllMain中使用TlsAlloc分配TLS索引,TLS索引就作为进程私有的全局变量被保存;以后,当该进程的新的线程使用DLL(Attahced to DLL),DllMain给它分配动态内存并且使用TlsSetValue把线程私有的数据按索引保存。DLL函数可以使用TlsGetValue按索引读取调用线程的私有数据。

TLS 函数如下:

DWORD TlsAlloc()

在进程或 DLL初始化时调用,并且把返回值(索引值)作为全局变量保存。

BOOL TlsSetValue(

DWORD dwTlsIndex, //TLS index to set value for

LPVOID lpTlsValue //value to be stored

);

其中:

dwTlsIndex TlsAlloc分配的索引。

lpTlsValue 是线程在TLS槽中存放的数据指针,指针指向线程要保存的数据。

线程首先分配动态内存并保存数据到此内存中,然后调用 TlsSetValue保存内存指针到TLS槽。

LPVOID TlsGetValue(

DWORD dwTlsIndex // TLS index to retrieve value for

);

其中:

dwTlsIndex TlsAlloc分配的索引。

当要存取保存的数据时,使用索引得到数据指针。

BOOL TlsFree(

DWORD dwTlsIndex // TLS index to free

);

其中:

dwTlsIndex TlsAlloc分配的索引。

当每一个线程都不再使用局部存储数据时,线程释放它分配的动态内存。在 TLS索引不再需要时,使用TlsFree释放索引。

线程同步

同步可以保证在一个时间内只有一个线程对某个资源(如操作系统资源等共享资源)有控制权。共享资源包括全局变量、公共数据成员或者句柄等。同步还可以使得有关联交互作用的代码按一定的顺序执行。

Win32 提供了一组对象用来实现多线程的同步。

这些对象有两种状态:获得信号 (Signaled)或者没有或则信号(Not signaled)。线程通过Win32 API提供的同步等待函数(Wait functions)来使用同步对象。一个同步对象在同步等待函数调用时被指定,调用同步函数地线程被阻塞(blocked),直到同步对象获得信号。被阻塞的线程不占用CPU时间。

同步对象

同步对象有: Critical_section(关键段),Event(事件),Mutex(互斥对象),Semaphores(信号量)。

下面,解释怎么使用这些同步对象。

关键段对象:

首先,定义一个关键段对象 cs

CRITICAL_SECTION cs;

然后,初始化该对象。初始化时把对象设置为 NOT_SINGALED,表示允许线程使用资源:

InitializeCriticalSection(&cs);

如果一段程序代码需要对某个资源进行同步保护,则这是一段关键段代码。在进入该关键段代码前调用 EnterCriticalSection函数,这样,其他线程都不能执行该段代码,若它们试图执行就会被阻塞。

完成关键段的执行之后,调用 LeaveCriticalSection函数,其他的线程就可以继续执行该段代码。如果该函数不被调用,则其他线程将无限期的等待。

事件对象

首先,调用 CreateEvent函数创建一个事件对象,该函数返回一个事件句柄。然后,可以设置(SetEvent)或者复位(ResetEvent)一个事件对象,也可以发一个事件脉冲(PlusEvent),即设置一个事件对象,然后复位它。复位有两种形式:自动复位和人工复位。在创建事件对象时指定复位形式。。

自动复位:当对象获得信号后,就释放下一个可用线程(优先级别最高的线程;如果优先级别相同,则等待队列中的第一个线程被释放)。

人工复位:当对象获得信号后,就释放所有可利用线程。

最后,使用 CloseHandle销毁创建的事件对象。

互斥对象

首先,调用 CreateMutex创建互斥对象;然后,调用等待函数,可以的话利用关键资源;最后,调用RealseMutex释放互斥对象。

互斥对象可以在进程间使用,但关键段对象只能用于同一进程的线程之间。

信号量对象

Win32中,信号量的数值变为0时给以信号。在有多个资源需要管理时可以使用信号量对象。

首先,调用 CreateSemaphore创建一个信号量;然后,调用等待函数,如果允许的话,则利用关键资源;最后,调用RealeaseSemaphore释放信号量对象。

此外,还有其他句柄可以用来同步线程:

文件句柄(FILE HANDLES

命名管道句柄(NAMED PIPE HANDELS

控制台输入缓冲区句柄(CONSOLE INPUT BUFFER HANDLES

通讯设备句柄(COMMUNICTION DEVICE HANDLES

进程句柄(PROCESS HANDLES

线程句柄(THREAD HANDLES

例如,当一个进程或线程结束时,进程或线程句柄获得信号,等待该进程或者线程结束的线程被释放。

等待函数

Win32 提供了一组等待函数用来让一个线程阻塞自己的执行。等待函数分三类:

等待单个对象的 (FOR SINGLE OBJECT):

这类函数包括:

SignalObjectAndWait

WaitForSingleObject

WaitForSingleObjectEx

函数参数包括同步对象的句柄和等待时间等。

在以下情况下等待函数返回:

同步对象获得信号时返回;

等待时间达到了返回:如果等待时间不限制 (Infinite),则只有同步对象获得信号才返回;如果等待时间为0,则在测试了同步对象的状态之后马上返回。

等待多个对象的 (FOR MULTIPLE OBJECTS)

这类函数包括:

WaitForMultipleObjects

WaitForMultipleObjectsEx

MsgWaitForMultipleObjects

MsgWaitForMultipleObjectsEx

函数参数包括同步对象的句柄,等待时间,是等待一个还是多个同步对象等等。

在以下情况下等待函数返回:

一个或全部同步对象获得信号时返回(在参数中指定是等待一个或多个同步对象);

等待时间达到了返回:如果等待时间不限制 (Infinite),则只有同步对象获得信号才返回;如果等待时间为0,则在测试了同步对象的状态之后马上返回。

可以发出提示的函数 (ALTERABLE)

这类函数包括:

MsgWaitForMultipleObjectsEx

SignalObjectAndWait

WaitForMultipleObjectsEx

WaitForSingleObjectEx

这些函数主要用于重叠 (Overlapped)I/O(异步I/O)。

MFC的线程处理

Win32 API的基础之上,MFC提供了处理线程的类和函数。处理线程的类是CWinThread,函数是AfxBeginThreadAfxEndThread等。

5-6解释了CWinThread的成员变量和函数。

CWinThread MFC线程类,它的成员变量m_hThreadm_hThreadID是对应的Win32线程句柄和线程ID

MFC 明确区分两种线程:用户界面线程(User interface thread)和工作者线程(Worker thread)。用户界面线程一般用于处理用户输入并对用户产生的事件和消息作出应答。工作者线程用于完成不要求用户输入的任务,如耗时计算。

Win32 API 并不区分线程类型,它只需要知道线程的开始地址以便它开始执行线程。MFC为用户界面线程特别地提供消息泵来处理用户界面的事件。CWinApp对象是用户界面线程对象的一个例子,CWinApp从类CWinThread派生并处理用户产生的事件和消息。

创建用户界面线程

通过以下步骤创建一个用户界面线程:

CWinThread派生一个有动态创建能力的类。使用DECLARE_DYNCREATEIMPLEMENT_DYNCREATE宏来支持动态创建。

覆盖 CWinThread的一些虚拟函数,可以覆盖的函数见表5-4关于CWinThread的部分。其中,函数InitInstance是必须覆盖的,ExitInstance通常是要覆盖的。

使用 AfxBeginThread创建MFC线程对象和Win32线程对象。如果创建线程时没有指定CREATE_SUSPENDED,则开始执行线程。

如果创建线程是指定了 CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。

创建工作者线程

程序员不必从 CWinThread派生新的线程类,只需要提供一个控制函数,由线程启动后执行该函数。

然后,使用 AfxBeginThread创建MFC线程对象和Win32线程对象。如果创建线程时没有指定CREATE_SUSPENDED(创建后挂起),则创建的新线程开始执行。

如果创建线程是指定了 CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。

虽然程序员没有从 CWinThread派生类,但是MFC给工作者线程提供了缺省的CWinThread对象。

AfxBeginThread

用户界面线程和工作者线程都是由 AfxBeginThread创建的。现在,考察该函数:MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,分别有如下的原型和过程:

用户界面线程的 AfxBeginThread

用户界面线程的 AfxBeginThread的原型如下:

CWinThread* AFXAPI AfxBeginThread(

CRuntimeClass* pThreadClass,

int nPriority,

UINT nStackSize,

DWORD dwCreateFlags,

LPSECURITY_ATTRIBUTES lpSecurityAttrs)

其中:

参数 1是从CWinThread派生的RUNTIME_CLASS类;

参数 2指定线程优先级,如果为0,则与创建该线程的线程相同;

参数 3指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;

参数 4是一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。

参数 5表示线程的安全属性,NT下有用。

工作者线程的 AfxBeginThread

工作者线程的 AfxBeginThread的原型如下:

CWinThread* AFXAPI AfxBeginThread(

AFX_THREADPROC pfnThreadProc,

LPVOID pParam,

int nPriority,

UINT nStackSize,

DWORD dwCreateFlags,

LPSECURITY_ATTRIBUTES lpSecurityAttrs)

其中:

参数 1指定控制函数的地址;

参数 2指定传递给控制函数的参数;

参数 345分别指定线程的优先级、堆栈大小、创建标识、安全属性,含义同用户界面线程。

AfxBeginThread 创建线程的流程

不论哪个AfxBeginThread,首先都是创建MFC线程对象,然后创建Win32线程对象。在创建MFC线程对象时,用户界面线程和工作者线程的创建分别调用了不同的构造函数。用户界面线程是从CWinThread派生的,所以,要先调用派生类的缺省构造函数,然后调用CWinThread的缺省构造函数。图8-1中两个构造函数所调用的CommonConstructMFC内部使用的成员函数。

 

CreateThread_AfxThreadEntry

MFC 使用CWinThread::CreateThread创建线程,不论对工作者线程或用户界面线程,都指定线程的入口函数是_AfxThreadEntry_AfxThreadEntry调用AfxInitThread初始化线程。

CreateThread _AfxThreadEntry在线程的创建过程中使用同步手段交互等待、执行。CreateThread由创建线程执行,_AfxThreadEntry由被创建的线程执行,两者通过两个事件对象(hEventhEvent2)同步:

在创建了新线程之后,创建线程将在 hEvent事件上无限等待直到新线程给出创建结果;新线程在创建成功或者失败之后,触发事件hEvent让父线程运行,并且在hEven2上无限等待直到父线程退出CreateThread函数;父线程(创建线程)因为hEvent的置位结束等待,继续执行,退出CreateThread之前触发hEvent2事件;新线程(子线程)因为hEvent2的置位结束等待,开始执行控制函数(工作者线程)或者进入消息循环(用户界面线程)。

MFC 在线程创建中使用了如下数据结构:

struct _AFX_THREAD_STARTUP

{

// 传递给线程启动的参数(IN

_AFX_THREAD_STATE* pThreadState;// 父线程的线程状态

CWinThread* pThread; // 新创建的MFC线程对象

DWORD dwCreateFlags; // 线程创建标识

_PNH pfnNewHandler; // 新线程的句柄

HANDLE hEvent; // 同步事件,线程创建成功或失败后置位

HANDLE hEvent2; // 同步事件,新线程恢复执行后置位

// 返回给创建线程的参数,在新线程恢复执行后赋值

BOOL bError; // 如果创建发生错误,TRUE

};

该结构作为线程开始函数的参数被传递给 _beginthreadex函数来创建和启动线程。_beginthreadex函数是“C”的线程创建函数,具有如下原型:

unsigned long _beginthreadex(

void *security,

unsigned stack_size,

unsigned ( __stdcall *start_address )( void * ),

void *arglist,

unsigned initflag,

unsigned *thrdaddr );

8-2描述了上述过程。图中表示,_AfxThreadEntry在启动线程时,将创建本线程的线程状态,并且继承父线程的模块状态。

 

 

线程的结束

从图 8-2可以看出,AfxEndThread用来结束调用它的线程:它将清理本线程创建的MFC对象和释放线程局部存储分配的内存空间;调用CWinThread的虚拟函数Delete;调用“C”的结束线程函数_endthreadex释放分配给线程的资源,但是不关闭线程句柄。

CWinThread::Delete 的缺省实现是:如果本线程的成员函数m_bDeleteTRUE,则调用“C”运算符号delete销毁MFC线程对象自身(delete this),这将导致线程对象的析构函数被调用。若析构函数检测线程句柄非空则调用CloseHandle关闭它。

通常,让 m_bDeleteTRUE以便自动地销毁线程对象,释放内存空间(MFC内存对象在堆中分配)。但是,有时候,在线程结束之后(Win32线程已经不存在)保留MFC线程对象是有用的,当然程序员自己最后要记得销毁该线程对象。

实现线程的消息循环

MFC中,消息循环是由线程完成的。一般地,可以使用MFC缺省的消息循环(即使用函数CWindThrad::Run),但是,有些时候需要程序员自己实现一个线程的消息循环,比如在用户界面线程进行一个长时间计算处理或者等待另一个线程时。一般有如下形式:

while ( bDoingBackgroundProcessing)

{

MSG msg;

while ( ::PeekMessage( &msg, NULL,0, 0, PM_NOREMOVE ) )

{

if ( !PumpMessage( ) )

{

bDoingBackgroundProcessing = FALSE;

::PostQuitMessage( );

break;

}

}

// let MFC do its idle processing

LONG lIdle = 0;

while ( AfxGetApp()->OnIdle(lIdle++ ) );

// Perform some background processing here

// using another call to OnIdle

}

该段代码的解释参见图 5-3对线程的Run函数的图解。

程序员实现线程的消息循环有两个好处,一是顾及了 MFCIdle处理机制;二是在长时间的处理中可以响应用户产生的事件或者消息。

在同步对象上等待其他线程时,也可以使用同样的方式,只要把条件

bDoingBackgroundProcessing

换成如下形式:

WaitForSingObject(hHandleOfEvent,0) == WAIT_TIMEOUT

即可。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
仿多线程的效果一般有2种办法:第一种是通过定时器;第二种是启动多线程,不同模式下启动函数不同,mfc与API与WIN32下面注意点也是有区别的! VC启动一个线程的三种方法,有需要的朋友可以参考下。 第一种AfxBeginThread() 用AfxBeginThread()函数来创建一个线程来执行任务,工作线程的AfxBeginThread的原型如下: CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,   LPVOID lParam,   int nPriority = THREAD_PRIORITY_NORMAL,   UINT nStackSize = 0,   DWORD dwCreateFlags = 0,   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL   );//用于创建工作线程 返回值: 成功时返回一个指向新线程线程对象的指针,否则NULL。 pfnThreadProc : 线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL; pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程. nPriority : 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级. nStackSize : 指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈 dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值: CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread 0 : 创建线程后就开始运行. lpSecurityAttrs : 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL, 那么新创建的线程就具有和主线程一样的安全性. 如果要在线程内结束线程,可以在线程内调用 AfxEndThread. 一般直接用AfxBeginThread(ThreadProc,this); 示例: UINT myproc(LPVOID lParam){CITTDlg *pWnd = (CITTDlg *)lParam; //将窗口指针赋给无类型指针pWnd->KMeansSegment(); //要执行的函数return 1;}void CITTDlg::KMeansSegment(){// 主要处理函数在这里写}void CITTDlg::OnKMeansSegment() //按钮点击执行{AfxBeginThread(myproc, (LPVOID)this);//启动新的线程} 注意,工作线程的函数必须是全局函数或静态成员函数,不能是普通的成员函数。 第二种CreateThread()函数原型为:HANDLECreateThread( NULL, // 没有安全描述符 0, // 默认线程栈的大小 MyThreadProc, // 线程函数指针,即函数名 (LPVOID)&n, // 传递参数 NULL, // 没有附加属性 NULL // 不需要获得线程号码 ); CreatThread,它返回的是一个句柄;如果不需要再监视线程,则用CloseHandle()关闭线程句柄。 线程的函数必须定义为: DWORD WINAPI MyThreadProc(LPVOID pParameter); 下面演示多线程操作控件,点击一个Button然后运行一个线程,将字符串显示在CEdit控件里面; 示例: .h头文件struct hS {CString Tmp;CTestDlg *hWnd; };//定义全局结构体,用来传递自定义消息DWORD WINAPI ThreadProc(LPVOIDlpParam);//线程函数声明,全局函数public: CString chtmp; struct hS *hTmp;protected: HANDLE m_hThread;//线程句柄 CEdit m_Edit;.cpp实现文件//线程执行函数DWORD WINAPI ThreadProc(LPVOID lpParam){//在这里写处理函数struct hS *Tmp2;Tmp2 = (hS*)lpParam;// 操作: Tmp2->hWnd->m_Edit.SetWindowText( (LPTSTR)Tmp2->Tmp );}void CTestDlg::OnBnClickedButton1(){ hTmp->Tmp = chtmp; hTmp->hWnd = this;//关键是把this指针传进去 m_hThread =CreateThread(NULL,0,ThreadProc,hTmp,0,NULL);//创建新线程 CloseHandle(m_hThread );} 用CreateThread()函数创建线程将返回一个线程句柄,通过该句柄你可以控制和操作该线程,当你不用时可以一创建该线程后就关闭该句柄,有专门的函CloseHandle()。关闭句柄不代表关闭线程,只是你不能在外部控制该线程(比如,提前结束,更改优先级等)。在线程结束后,系统将自动清理线程资源,但并不自动关闭该句柄,所以线程结束后要记得关闭该句柄。 第三种_beginthread() 函数原型为:intptr_t _beginthread( void( *start_address )( void * ), //指向新线程调用的函数的起始地址 unsigned stack_size, //堆栈大小,设置0为系统默认值 void *arglist //传递给线程函数的参数,没有则为NULL ); 返回值: 假如成功,函数将会返回一个线程的句柄,用户可以像这样声明一个句柄变量存储返回值:   HANDLE hStdOut = _beginthread( CheckKey, 0, NULL )。如果失败_beginthread将返回-1。所在库文件: #include 线程函数的定义: 对于_beginthread()创建的线程,其线程函数定义为: void ThreadPro(void * pArguments ); _beginthreadex()为_beginthread()的升级版。 总结:AfxBeginThreadMFC的全局函数,是对CreateThread的封装。 CreateThread是Win32 API函数,AfxBeginThread最终要调到CreateThread。而_beginthread是C的运行库函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值