MFC多线程编程
一、创建线程函数:
函数原型:
HANDLEWINAP lCreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadld);
参数说明:
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数表示线程空间大小。传入0表示使用默认大小(1MB)。
第三个参数表示新线程所执行的线程函数的地址,多个线程可以使用同一个函数地址。
一、创建线程函数:
函数原型:
HANDLEWINAP lCreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadld
);
第四个参数是传给线程函数的参数。
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,知道ResumeThread().
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程的ID号。
函数返回值:成功后返回新线程的句柄,失败返回NULL。
二、WaitForSingleObject
函数功能:等待函数-使线程进入等待状态,直到指定的内核对象被触发。
函数原型:
DWORD WINAPI WaitForSingleObeject(
HANDLE hHandle,
DWORD dwMilliSeconds
);
参数说明:
第一个参数为要等待的内核对象。
第二个参数为最长的等待时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回。传入INFINITE就表示无限等待。
函数返回值:
在指定的时间内触发,函数返回WAIT_OBJECT_0。
超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT.传入参数有错误将返回WAIT_FAILED
例子:
//子线程函数
DWORD WINAPI ThreadFun(LPVOID pM)
{
cout << “子线程的线程ID号为:”<< GetCurrentThreadId() <<"\n子线程输出Hello World : " << endl;
return 0;
}
//主函数:主线程执行函数
int main()
{
cout << "创建多线程\n ";
cout << “更多的windows32\n”;
HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForSingleObject(handle, INFINITE);
system("pause");
return 0;
}
共享变量问题:(当线程A正在函数中运行的时候,此时线程B进来了,他改动了每个变量,这是就有可能会影响线程A的运行。
解决方案:让每个线程都拥有自己专用的一块内存区域来供标准C运行库中所有有需要的函数使用。
这块内存区域的创建有C/C++运行库函数_beginThreadex()负责。
子线程函数。
_beginThreadex()函数
在创建新线程时会分配并初始化一个_tiddata块。这个块自然是用来存放一些需要线程独享的数据。
新线程运行是会首先将_tiddata块与自己进一步关联起来
这样每一个线程就只会访问和修改自己的数据而不会去串改其他线程的数据了。
如果在代码中使用标准的C运行库中的函数时,尽量使用_beginThreadex()来代替CreateThread().
例子:
unsigned int _stdcall ThreadFun(LPVOID pM)
{
cout << “线程ID号为” << GetCurrentThreadId()<<"的子线程说: Hello World \n ";
return 0;
}
//主函数:主线程执行函数
int main()
{
const int THREAD_NUM = 5;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM;i++)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
}
WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
system(“pause”);
return 0;
}
举个例子:
DWORD WINAPI FunProc(LPVOID lpParameter)
{
while(true)
{
if(tickets>0)
{
cout<<"Thread 1 sell ticket: "<<tickets–<<endl;
}
else
break;
}
return 0;
}
上面这个函数例子是一个火车票售票的的例子。
虽然可以使每个线程访问他们自己的数据。但是当多个线程需要使用同一个全局变量的时候。
如上面这个函数:假设tickes=1;当线程1进来发现tickes>0,线程1进入if语句,如果当thread1还没有进行tickes–,此时线程2进来了,发现tickes还是大于0的,所以他也进来了,这样就会让tickes被自减两次,从而得到-1,一个错误的结果。
如何预防上面的情况呢?只要当某一个线程访问一段代码片段,其他线程不能访问就行了。
二、通过互斥量解决上面的问题CreateMutex
函数原型:
HNADLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
第一个参数是安全结构,默认是NULL不能继承句柄;
第二个参数为FALSE时创建Mutex时不指定所有权,若为TRUE则指定为当前的创建线程ID为所有者,其他线程访问需要先ReleaseMutex;
第三个参数用于设置Mutex名,为NULL时表示匿名互斥量
WaitForSingleObject(); 请求一个互斥量的访问权;
ReleaseMutex();释放一个互斥量的访问权。
上面函数的改进:
//声明全局变量
HANDLE hMutex;
//在main函数中,创建互斥变量mutex,不继承,无线程所有权,匿名
hMutex = CreateMutex(NULL, FALSE, NULL);
//Fun函数的改进如下
DWORD WINAPI FunProc(LPVOID lpParameter)
{
while(true)
{
WaitForSingleObject(hMutex, INFINITE);//如果互斥变量没有被释放则一直等待
if(tickets>0)
{
cout<<"Thread 1 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(ERROT_ALREADY_EXISTS == GetLastError())
{
cout<<“Only one instance can run !”<<endl;
system(“pause”);
return 0;
}
}