进程是系统中的重要概念,简单来说字面的意思就是一个运行中的程序,但是程序代表的是静态的指令代码。进程由系统管理的内核对象和存放程序运行资源的地址空间组成。内核对象由系统管理,因此应用程序是无法直接访问的;地址空间中则包含着程序运行所需的所有资源,如可执行模块、DLL、代码和数据,以及动态分配的栈与堆。可以说,其实进程就是程序运行的资源的容器。但是进程只是为程序的执行提供了一个场所,真正实现执行流程的是线程。每个进程启动时都会自动建立一个线程作为主线程,由它去创建其他的线程。线程与进程相比更为轻量级,二者的主要联系在于:
1. 线程创建在进程的地址空间中,对于进程资源有着完全访问权限,多线程间共享资源,可自由通信;
2. 线程建立时也有自己的内核对象和线程栈,而非地址空间;
3. 一般需要实现多任务时我们更推荐使用线程实现,因为创建一个进程需要分配地址空间,而且进程间的切换也很不方便,即使用进程开销很高;
一、创建多线程
简单了解了进程与线程的概念之后,我们来看看如何在程序中创建线程。Windows SDK为我们提供了创建线程的专用函数:CreateThread()
-
HANDLE CreateThread(
-
LPSECURITY_ATTRIBUTES lpsa,
-
DWORD cbStack,
-
LPTHREAD_START_ROUTINE lpStartAddr,
-
LPVOID lpvThreadParam,
-
DWORD fdwCreate,
-
LPDWORD lpIDThread
-
);
-1-第一个参数是安全属性结构,主要控制该线程句柄是否可为进程的子进程继承使用,默认使用NULL时表示不能继承;若想继承线程句柄,则需要设置该结构体,将结构体的bInheritHandle成员初始化为TRUE;
-2-cbStack表示的线程初始栈的大小,若使用0则表示采用默认大小初始化;
-3-lpStartAddr表示线程开始的位置,即线程要执行的函数代码,这点有点类似于回调函数的使用;
-4-lpvThreadParam用来接收线程过程函数的参数,不需要时可以设置为NULL;
-5-fdwCreate表示创建线程时的标志,CREATE_SUSPENDED表示线程创建后挂起暂不执行,必须调用ResumeThread才可以执行,0表示线程创建之后立即执行
-6-lpIDThread用来保存线程的ID;
了解了CreateThread函数的用法,我们来看一个例子实际体验一下。下面的例子会开启一个线程循环输出信息,我们可以查看结果:
-
//MultiThread
-
-
#include <iostream>
-
#include <cstdlib>
-
#include <windows.h>
-
using namespace std;
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
-
-
-
int main()
-
{
-
-
int j = 0;
-
-
HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
-
CloseHandle(hThread_1);
-
-
while (j++ < 1000)
-
cout << "MainThread is running for" << " the "<< j <<" times "<<endl;
-
-
system("pause");
-
return 0;
-
-
-
}
-
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
-
{
-
int i = 0;
-
while (i++ < 1000)
-
cout << "Thread 1 is running for" <<" the "<< i <<" times "<<endl;
-
-
return 0;
-
}
由于我们使用system("pause")命令,因此我们不需要Sleep()命令同样可以看到主线程和分线程的交替执行:
二、多线程的问题
上面的程序我们只是建立一个新线程,如果建立两个呢?下面我们模拟一个火车售票的模型,线程1和线程2同时负责售票,主线程负责平台搭建。线程1和线程2分别访问全局变量tickets,输出其值作为票号然后将其值减一,直至“售完”所有票:
-
//MultiThread
-
-
#include <iostream>
-
#include <cstdlib>
-
#include <windows.h>
-
using namespace std;
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
-
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
-
-
int tickets = 100;
-
-
int main()
-
{
-
-
HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
-
HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
-
CloseHandle(hThread_1);
-
CloseHandle(hThread_2);
-
-
system("pause");
-
return 0;
-
-
-
}
-
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
-
{
-
-
while (true)
-
{
-
if (tickets > 0)
-
{
-
Sleep(10);
-
cout << "Thread 1 sell ticket : "<<tickets--<<endl;
-
}
-
else
-
break;
-
}
-
return 0;
-
}
-
-
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
-
{
-
-
while (true)
-
{
-
-
if (tickets > 0)
-
{
-
Sleep(10);
-
cout << "Thread 2 sell ticket : "<<tickets--<<endl;
-
}
-
else
-
break;
-
-
}
-
return 0;
-
}
再次运行之后结果:
分析下上面的结果,首先线程1和线程2确实交替运行“购票”了,其次看到不规则的输出,1314连到一起。关于这点我们要知道由于CPU是执行分片处理的,即不同的线程会得到不同的时间片来执行程序,尤其是对于单CPU来说更是如此,因此当线程1执行到"Thread 1 sell ticket"时结束分片,由线程2继续执行;线程2执行到同样位置再次交还给线程1继续执行显示“13”,然后线程2执行显示“14”。这样看好像也没有问题,但是问题在于最后出现了“0”,我们的代码中是不允许出现0的,之所以这样,同上面的元婴一样,在线程1执行到ticket = 1时交接给了线程2执行,并且输出了其值1,然后线程1继续输出了当前值0.此时的if条件就失去作用了。虽然这种情况不一定总是发生,但是在实际的操作中是很有可能出现的,而且排查起来也很困难。那么我们该如何解决呢?
问题的关键在于线程1和线程2同时访问了全局变量tickets,导致了错误;那么我们的解决方案就是将全局变量的访问控制起来就可以了,不允许同时有多个线程同时访问该变量。我们使用互斥量来实现。
三、互斥量的应用
使用互斥量并不困难,核心步骤如下:
-1-CreateMutex创建一个互斥量:
-
HANDLE CreateMutex(
-
LPSECURITY_ATTRIBUTES lpMutexAttributes,
-
BOOL bInitialOwner,
-
LPCTSTR lpName
-
);
第一个参数同样是安全结构,默认是NULL不能继承句柄;第二个参数为FALSE时创建Mutex时不指定所有权,若为TRUE则指定为当前的创建线程ID为所有者,其他线程访问需要先ReleaseMutex;第三个参数用于设置Mutex名,后续我们会说明,为NULL时表示是匿名互斥量。
-2-WaitForSingleObject():请求一个互斥量的访问权;
-3-ReleaseMutex():释放一个互斥量的访问权;
好了,我们再来看看应用了互斥量的改进程序:
-
//MultiThread
-
-
#include <iostream>
-
#include <cstdlib>
-
#include <windows.h>
-
using namespace std;
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
-
DWORD WINAPI Fun2Proc(LPVOID lpParameter);
-
-
int tickets = 100;
-
HANDLE hMutex;
-
-
int main()
-
{
-
-
-
hMutex = CreateMutex(NULL, FALSE, NULL);
-
-
HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
-
HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
-
CloseHandle(hThread_1);
-
CloseHandle(hThread_2);
-
-
-
system("pause");
-
return 0;
-
-
-
}
-
-
-
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
-
{
-
-
while (true)
-
{
-
WaitForSingleObject(hMutex, INFINITE);
-
if (tickets > 0)
-
{
-
Sleep(10);
-
cout << "Thread 1 sell ticket : "<<tickets--<<endl;
-
}
-
else
-
break;
-
ReleaseMutex(hMutex);
-
}
-
return 0;
-
}
-
-
DWORD WINAPI Fun2Proc(LPVOID lpParameter)
-
{
-
-
while (true)
-
{
-
WaitForSingleObject(hMutex, INFINITE);
-
if (tickets > 0)
-
{
-
Sleep(10);
-
cout << "Thread 2 sell ticket : "<<tickets--<<endl;
-
}
-
else
-
break;
-
ReleaseMutex(hMutex);
-
}
-
return 0;
-
}
这次运行之后的结果不会出现上面的错误了:
互斥量还有一个小应用,利用命名互斥量来保证只有一个程序实例运行。我们可以创建一个命名互斥量,当程序要重复运行时,检查互斥量的返回值,若为ERROR_ALREADY_EXISTS则表示已经有一个实例运行了,直接return即可。在源程序中添加以下代码:
-
//确保只有一个实例运行
-
HANDLE hMutex_1 = CreateMutex(NULL, TRUE, "tickets");
-
if (hMutex_1)
-
{
-
if (ERROR_ALREADY_EXISTS == GetLastError())
-
{
-
cout << "Only one instance can run !" << endl;
-
system("pause");
-
return 0;
-
}
-
}
阅读(5301) | 评论(0) | 转发(0) |