线程是进程中的一个执行单位(每个进程至少有一个主线程),一个进程可以有多个线程,而一个线程只存在于一个进程中。在数据关系上属于一对多的关系。线程不占有系统资源,它所使用的资源全部由所属进程向系统申请。
在多处理器中,不同的线程可以同时运行在不同的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;
}
好了,多线程方面的知识博大精深,就先分享到这儿了。