更新时间:2015-09-02 08:03:18浏览次数:1+次
我们多少听过“进程”,”线程“这样的词语,这是计算机上的一些专业术语,进程顾名思义就是正在进行的程序,而线程则是计算机上的最小执行单位,计算机执行进程时本质上是在执行线程(这是个人的理解)。因此我们这篇文章重点讲解下线程。
我最开始接触线程是在孙鑫的MFC视频中,当时听得迷迷糊糊的,特别难懂,尤其是讲到进程同步的实现利用到的那些同步函数,那可真是煎熬啊。由于不懂,就只能百度,可是百度也不详细,就问人咯,别人讲的也不清楚,于是,一直云雾里;我们学习过程中会遇到很多的困难,我们需要人的帮助,讲解,我特别理解这种心情,因此,在学习过程中,我把自己的学习心得记录下来,与君共勉,希望多少能帮上你。
上正题:我们使用线程自然需要创建线程,要不然哪来的线程?来!看代码:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes
描述施行于这一新线程的 security 属性。NULL 表
示使用缺省值。此参数在
Windows 95 中被忽略。
dwStackSize 新线程拥有自己的堆栈。0 表示使用缺省大小:
1MB。
lpStartAddress 新线程将开始的起始地址。这是一个函数指针。(译
注:在 C
语言中函数名称即代表函数指针,所以这里可放一个函数名称)
lpParameter 此值将被传送到上述所指定之新线程函数去,作为参数。
dwCreationFlags 允许你产生一个暂时挂起的线程。默认情况是“立即开始执行”。
lpThreadId 新线程的 ID 会被传回到这里。
返回值:
如果
CreateThread( ) 成功,传回一个 handle,代表新线程。否则传回一个FALSE。如果失败,你可以调用 GetLastError( )获知原因。
记住,如果函数参数有缺省值,我们最好使用它。
知道吗?刚开始接触线程时,我把C语言中的函数调用与线程函数的使用完全搞混了,以为两者就是一个意思,其实不是,来我们看个对比:
#include <stdio.h>
int square(int n);
void main( )
{
int result; (1)
result = square(5); (2)
printf("%d\n", result); (5)
}
int square(int n)
{
int product = n * n; (3)
return product; (4)
}
#include <windows.h>
DWORD WINAPI ThreadFunc(LPVOID);
void main( )
{
HANDLE hThread; (1)
DWORD threadId;
hThread = CreateThread(NULL,
0,
ThreadFunc,
0,
0,
&threadId ); (2)
printf("Thread running");(4)
}
DWORD WINAPI ThreadFunc(LPVOID p)
{
// ... (3)
}
面对一个函数调用操作,控制权会转移到被调用函数中,执行完毕后再返回原调用处。为了印出平方值,square( )必须回到 main( )。多线程范例中,我们不直接调用 ThreadFunc( ),我们调用的是
CreateThread( )并交给它 ThreadFunc( )地址。 CreateThread( )开启一个新的线程,该线程调用
ThreadFunc( )。而原来的线程继续前进。换句话说,ThreadFunc()被异步地 (asynchronously)执行了,意思是
ThreadFunc( )不需要在 main( )继续前进之前完成。所以,返回值也就不可能像传统方式那样。
接下来我们看个程序,来说说,线程的一些”秘密“:
#include "windows.h"
#include "stdio.h"
DWORD WINAPI myfun1(LPVOID lpParameter);
DWORD WINAPI myfun2(LPVOID lpParameter);
int main()
{
HANDLE handle1,handle2;
handle1=CreateThread(NULL,0,myfun1,NULL,0,NULL);
printf("线程1开始运行!\n");
handle1=CreateThread(NULL,0,myfun2,NULL,0,NULL);
printf("线程2开始运行!\n");
::CloseHandle(handle1);
::CloseHandle(handle2);
while(1)
{
if(getchar()=='q')
{
return 0;
}
else{
::Sleep(3000);
}
}
}
DWORD WINAPI myfun1(LPVOID lpParameter)
{
printf("线程1正在运行!\n");
// ::Sleep(3000);
return 0;
}
DWORD WINAPI myfun2(LPVOID lpParameter)
{
printf("线程2正在运行!\n");
// ::Sleep(3000);
return 0;
}
每次的运行结果都不一样,为什么?这是因为“时间切片”,你可以试试,我们运行程序后,它得结果会不一样,与我们用函数调用的思想来解释完全不同,这是因为,我们的计算机会给我们的线程分配时间切片,也就是一个小程序运行的时间限制,所以我们每次运行的结果不一样,具体的关于线程时间片的解说,请到本站的资源下载---->书籍中下载侯俊杰的《深入浅出MFC》。
相信讲到现在你也懂了点,现在我们开始下面的讲解---线程同步。线程同步是指统一进程中的多个线程互相协调工作达到一致性。通俗点说就是,因为时间片的存在,导致现成的执行也不确定,因此我们引入线程同步函数,使得当一个线程程序,其他的线程程序则处于等待状态。线程同步我们可以用四种函数来实现:
事件(events)
信号量(semaphores)
互斥器(mutexes)
管道(Pipes。分为 named 和 anonymous 两种)
不过我在这里只讲解互斥器:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, 安全属性结构指针,可为NULL,表示默认安全性
BOOL bInitialOwner, //拥有互斥对象的线程(一般是主线程)是否占有该互斥量,TRUE:占有,FALSE:不占有
LPCTSTR lpName //设置互斥对象的名字
);
首先创建互斥对象。我们写一个程序,上代码:
#include <windows.h> //包含头文件
#include <stdio.h>
DWORD WINAPI myfun1(LPVOID lpParameter);//声明线程函数
DWORD WINAPI myfun2(LPVOID lpParameter);
HANDLE hmutex;
int a=0; //定义全局变量a
int main()
{
hmutex=::CreateMutex(NULL,FALSE,NULL); //创建互斥对象并返回其句柄
HANDLE h1,h2; //定义线程句柄
h1=::CreateThread(NULL,0,myfun1,NULL,0,NULL); //创建线程1
printf("线程1开始运行!\r\n");
h2=::CreateThread(NULL,0,myfun2,NULL,0,NULL); //创建线程2
printf("线程2开始运行!\r\n");
::CloseHandle(h1); //关闭线程句柄对象
::CloseHandle(h2);
::Sleep(10000); //程序睡眠10秒
return 0;
}
DWORD WINAPI myfun1(LPVOID lpParameter) //线程函数1
{
while(1)
{
::WaitForSingleObject(hmutex,INFINITE); //请求互斥对象
if(a<10000)
{
a+=1; //变量自加
::Sleep(1000); //线程睡眠1秒
printf("线程1:%d\r\n",a);
::ReleaseMutex(hmutex); //释放互斥对象句柄
}
else
{
::ReleaseMutex(hmutex); //释放互斥对象句柄
break;
}
}
return 0;
}
DWORD WINAPI myfun2(LPVOID lpParameter) //线程函数2
{
while(1)
{
::WaitForSingleObject(hmutex,INFINITE); //请求互斥对象
if(a<10000)
{
a+=1;
::Sleep(1000);
printf("线程2:%d\r\n",a); //输出变量
::ReleaseMutex(hmutex); //释放互斥对象句柄
}
else
{
::ReleaseMutex(hmutex); //释放互斥对象句柄
break; //跳出循环
}
}
return 0; //线程正常退出
}
waitforsingleobject(....)函数就是用来等待线程,如果该线程没有信号,就不能激活waitforsingleobject(),就会卡在线程这里,不会在往下执行,直到接收到信号,我们看下代码吧:
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
hHandle[in]对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds[in]定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。
接下来就是释放互斥对象,我们创建了互斥对象,最后得释放它,要不然你霸着它不放,别人怎么用,对不对?所以我们释放互斥对象,其实还有更深入的原因,每当我们创建互斥对象时,就相当于给它自增一,释放它就等于自减一,为什么会自增自减呢?我们不做讨论,有兴趣的,可以看看孙鑫的MFC视频,详细了解。
可能你觉得我写这个代码没什么用,其实我们现在学的任何东西以后都能用上,我们可以用线程的同步来实现聊天程序,还有其他用途哦!这就不详述了。以后我们会用到的.......
在这里,我强烈推荐《win32多线程程序设计》《windows核心编程》都是对线程的详细描述,本网站提供下载,路径同上,欢迎哦!!!!