然而,多任务环境下,多个线程会随时被暂停,然后切换到其他线程执行。这会给程序带来很多问题。可能是逻辑错误,也可能引起内存访问错误。
这个问题是多线程环境必须解决的问题。所以解决起来还是比较轻松的,因为都有现成的处理办法。问题是,你要知道如何去处理。更重要的是,你要知道为什么要这么处理。这个在前面的文章都有所说明,在这里我们讨论下公共资源访问的问题,解决这个问题就可以大大完善任务机制。然后在此基础上,你可以再添砖加瓦,做的越来越好,最后甚至可以拿去实际项目中使用,而不必依赖Windows内部提供的线程池,只要你肯学习,肯钻研,你是可以写出一套不错的线程池框架的。
所有线程的公共的资源在这里就是全局变量的任务列表了。主线程要给这个列表添加任务,线程池线程要获取任务。所以,多个线程在操作这个任务列表的时候,就需要互斥的操作了。当主线程在添加任务的时候,其他线程不能去判断是否有任务和取出任务。当一个线程池线程在取出或者判断任务个数的时候,其他线程也不能操作,也不能判断。这样就可以保证互斥的使用了。
我们不能使用if这样的代码来执行互斥的操作限制。因为线程切换是系统来切换的,系统的线程在执行线程调度的时候,你这个if都无法跑起来,你怎么能用if去限制呢?我们必须利用系统的机制,告诉系统,这个是公共资源,我占用了之后,就要锁定,其他线程不能趁我不在的时候去操作。我们利用了系统的机制,系统就会帮我们照看我们锁定的资源,其他线程是无法操作的。
我们使用的是关键段。这是线程同步的一种方式,可以实现互斥的操作。关键段可以让我们在 一段代码执行的时候,不被切换,保证一段代码执行完后才可以被切换。这样的话,我们操作公共资源的时候,使用关键段就可以确保此时线程不会被强行的暂停然后切换成另一个线程执行。这样我们线程独占CPU操作完公共资源后,再被切换也就无所谓了。因为我们已经操作完了公共资源了。
所有线程都以这样的方式来防止自己在操作公共资源的时候被切换,从而达到了交叉操作公共资源带来的混乱。而且关键段可以让我们一段代码的执行不受打扰,这个特性非常不错。
我们要在所有操作公共资源的代码上加上关键段保护。关键段的更多的解释,就不在这里说了,你可以参考《Windows核心编程》上的关键段的解释,或者看我后续写的核心编程相关的系列文章的详细解释。
这里解释一下使用关键段的方法,使用好了关键段,任务机制也就完善了。首先要创建一个关键段的结构体,这是关键段的内部操作需要的东西。因为关键段在多个线程函数中使用,所以需要是全局的。创建这个结构体变量的代码如下:
CRITICAL_SECTION g_cs;
然后我们在主线程开始后初始化一下这个结构体,调用函数InitializeCriticalSection,代码如下:InitializeCriticalSection(&g_cs);
然后在要操作公共资源的代码前加上:EnterCriticalSection(&g_cs);
在操作公共资源结束后的加上:LeaveCriticalSection(&g_cs);
这样就可以了。比如添加任务的时候,我们的代码如下:EnterCriticalSection(&g_cs);//进入保护状态
g_tasklist.push_back(0);
g_tasklist.push_back(0);
LeaveCriticalSection(&g_cs);//退出保护状态
进入保护状态后,操作g_tasklist不会被其他线程干扰而导致共享资源操作错误。需要注意的是,一个线程执行了EnterCriticalSection(&g_cs)后,只要还没有执行LeaveCriticalSection(&g_cs),其他线程执行EnterCriticalSection(&g_cs)时就被堵住了,无法向后执行。等退出了保护状态,其他线程才可以突破EnterCriticalSection(&g_cs)的阻碍。正是因为这个可以阻碍其他线程,所以,你最好只在公共资源操作的时候加上保护,其他代码就不要放在中间,否则阻碍了其他线程的执行,就会降低效率了。对于关键段的时候,其实很简单,我不在此多说了,详细的会在其他文章说明。如果有不清楚的,可以留言。
下面是运行的截图:
#include <Windows.h>
#include <iostream>
#include <vector>
using namespace std;
CRITICAL_SECTION g_cs;
vector<int> g_tasklist;
void TaskProc(int i)
{
cout<<"线程"<<i<<"正在";
cout<<"假装处理任务,并且很快处理完了一个,剩余任务数:"<<g_tasklist.size()<<endl;
}
DWORD WINAPI ThreadPoolProc(LPVOID lpParam)
{
while(1)
{
EnterCriticalSection(&g_cs);
if (g_tasklist.size()<=0)
{
LeaveCriticalSection(&g_cs);
cout<<"没有可处理任务,线程"<<*(int*)lpParam<<"开始休眠\n";
Sleep(5000);
}
else
{
g_tasklist.pop_back();
LeaveCriticalSection(&g_cs);
TaskProc(*(int*)lpParam);
Sleep(1000);
}
}
}
void main()
{
HANDLE hThread[5]={0};
DWORD dwThreadID[5]={0};
static int id[5];
for (int i=0;i<5;i++)
{
id[i]=i;
hThread[i]=CreateThread(NULL,0,ThreadPoolProc,&id[i],0,&dwThreadID[i]);
}
InitializeCriticalSection(&g_cs);
while(1)
{
EnterCriticalSection(&g_cs);
g_tasklist.push_back(0);
g_tasklist.push_back(0);
LeaveCriticalSection(&g_cs);
cout<<"新增2个任务.\n";
Sleep(2000);
}
}