线程是进程中的一个执行单位(每个进程至少有一个主线程),一个进程可以有多个线程,而一个线程只存在于一个进程中。在数据关系上属于一对多的关系。线程不占有系统资源,它所使用的资源全部由所属进程向系统申请。
在多处理器中,不同的线程可以同时运行在不同的CPU上,这样可以提高程序的运行效率。除此之外,有些时候必须使用多线程。例如,杀毒软件在查杀病毒的时候,它需要一边扫描相关的磁盘文件,一边显示当前的扫描进度以及发现的问题。如果把这几个工作放在一个线程中执行,会让程序看上去像卡住一样。在这种情况下,分为多个线程,使用不同的线程完成不同的工作,就可以协同达到预想的效果。
创建线程所用的API:
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,//指明创建线程的安全属性,为指向SECURITY_ATTRIBUTES结构体的指针,该参数一般设置为NULL SIZE_T dwStackSize,//指定线程使用的堆栈大小,如果为NULL,则与主线程栈相同 LPTHREAD_START_ROUTINE lpStartAddress,//指定线程函数,线程从该函数的入口处开始运行,函数返回时就意味着线程终止运行,该函数属于回调函数。 /* 线程回调函数的定义形式如下: DWORD WINAPI ThreadProc( LPVOID lpParameter ); 函数的返回值DWORD类型,该函数只有一个参数,该参数由CreateThread函数给定。该函数的函数名可以任意定义。 */ LPVOID lpParameter,//该参数表示传递给线程函数的一个参数,可以使指向任意数据类型的指针 DWORD dwCreationFlags,//该参数指明创建线程后的状态,在创建线程后可以让线程立即执行,也可以让线程处于暂停状态。如果需要立即执行,设置为0;如果要让线程处于暂停状态,设置为CREATE_SUSPENED,需要线程执行时调用ResumeThread函数来即可。 LPDWORD lpThreadId//该参数用于返回新创建线程的线程ID );
尝试写一个多线程的例子,代码如下:
#include <windows.h> #include <iostream> using namespace std; DWORD WINAPI ThreadProc(LPVOID lpParam) { cout<<"In ThreadProc"<<endl; return 0; } int main() { HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //代码插入处1 cout<<"In main"<<endl; CloseHandle(hThread); return 0; }
运行结果1如下:
运行结果2如下:
对于第一种情况,其中只输出了“In main”,而我们指定的线程函数并没有执行输出。这是因为主线程在CPU分给它的第一个时间片就执行完了,主线程执行完就意味着整个程序结束了,而新创建的那个线程甚至都没有执行的机会。
针对这种情况,我们可以使用如下的API使得新创建的线程有机会执行自己的线程函数。
DWORD WaitForSingleObject( HANDLE hHandle,//要等待的句柄对象 DWORD dwMilliseconds//指定等待超时毫秒数,如果设置为0,则立即返回,如果设置为INFINITE,则表示一直等待线程函数的返回。INFINITE是系统定义的一个宏,其定义如下: #define INFINITE 0xFFFFFFFF );
如果该函数失败,返回WAIT_FAILED;如果等待的对象变成激发状态,则返回WAIT_OBJECT_0;如果等待对象变成激发状态之前,等待时间结束了,则返回WAIT_TIMEOUT。
我们在代码标号1处添加如下语句:
WaitForSingleObject(hThread, INFINITE);
则其运行结果如下:
对于第二种情况,可能第一眼看上去有点晕,这输出的是什么?其实是两个线程在使用输出流缓冲区的时候交错了。大家都往输出缓冲区中添加数据,所以最好输出时是这两个线程要输出数据的“混合体”,既不是a线程要输出的数据,也不是b线程要输出的数据。针对这种情况怎么办呢?既然不能让两个线程同时使用,那就让它们分开使用就行了。
可以使用临界区来解决该问题。临界区对象是一个CRITICAL_SECTION的数据结构,Windows操作系统使用该数据结构对关键代码进行保护,以确保多线程下的共享资源可以被正确使用。在同一时间内,Windows只允许一个线程进入临界区。
操作临界区的函数有4个:
初始化临界区
VOID InitializeCriticalSection( LPCRITICAL SECTION lpCriticalSection );
进入临界区
VOID EnterCriticalSection( LPCRITICAL SECTION lpCriticalSection );
离开临界区
VOID LeaveCriticalSection( LPCRITICAL SECTION lpCriticalSection );
删除临界区
VOID DeleteCriticalSection( LPCRITICAL SECTION lpCriticalSection );
其中,这4个API的参数都是指向CRITICAL_SECTION结构体的指针。
此时,我们可以把代码修改为如下形式:
#include <windows.h> #include <iostream> using namespace std; CRITICAL_SECTION g_cs;//定义临界区对象 DWORD WINAPI ThreadProc(LPVOID lpParam) { EnterCriticalSection(&g_cs);//进入临界区 cout<<"In ThreadProc"<<endl; LeaveCriticalSection(&g_cs);//离开临界区 return 0; } int main() { InitializeCriticalSection(&g_cs); HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); EnterCriticalSection(&g_cs); cout<<"In main"<<endl; LeaveCriticalSection(&g_cs); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); DeleteCriticalSection(&g_cs); return 0; }
此时执行结果如下:
有时候,我们可能会创建不止一个线程用于其它的工作,这时候有怎么协调好它们呢?主要是两点:1.在使用公共资源的时候记得要让它们互斥使用。2.保证所有的工作做完了之后再退出程序。
对于第二点,有一个可以等待多个指定句柄的API:
DWORD WaitForMultipleObjects( DWORD nCount,//用于指明想要让函数等待的线程数量。这个值需要在1和MAXIMUM_WAIT_OBJECTS之间。 CONST HANDLE *lpHandles,//指向等待线程句柄的数组指针 BOOL fWaitAll,//表示是否等待全部线程的状态完成,如果设置为TRUE,则等待全部 DWORD dwMilliseconds//等待超时毫秒数,与WaitForSingleObject函数中用法相同 );
则,创建多个线程的代码轮廓大致如下:
定义一个临界区; DWORD WINAPI ThreadProc(LPVOID lpParam) { 进入临界区 执行在共享资源中的所需操作; 离开临界区 可能还有其它非临界区中的操作; } int main() { 初始化一个临界区; HANDLE hThread[N] = {0}; for(int i = 0; i < N; ++i) { hThread[i] = CreateThread(…); } WaitForMultipleObjects(N, hThread, TRUE, INFINITE); ……//一些其它的操作 for(i = 0; i < N; ++i ) { CloseHandle(hThread(i)); } 删除临界区; return 0; }
好了,多线程方面的知识博大精深,就先分享到这儿了。