正文

Windows程序设计[2]-线程与线程通信2006-03-20 09:40:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/rickone/11194.html

分享到:

首先要说明一点,我在这里写的关于Win32编程的Windows程序设计系列文章并不是我深思熟虑的小论文,实际上我在写的时候还不了解Win32编程,这是我自己在学习。如果您是初学者禁止观看,如果您是高手欢迎指导!除此之外的其它版的有些文章我是非常认真写的。

Windows程序启动的过程
在Win32环境下有两种模式,GUI和CUI,前者是图形用户接口,后者是控制台用户接口(类似DOS),GUI后面会要深入讨论,如果Windows没这个东西,估计就没人用它了。学过C语言的都知道任何一个程序都要有一个main(){}入口函数,但OS(操作系统)并不是直接调用main();,而是先调用C/C++运行期启动函数,此函数会初始化C/C++运行期库。我先不去理解这里的两个概念,我觉得这里的C/C++运行期使用的目的是为了实现C++的一种机制,比如malloc()管理自己的存储空间,分配对象,回收对象等。OK先不管它如何做的,总之它做好了运行一个Win32程序的一切准备工作,然后再建立一个进程,该进程有一个主线程,主线程的入口函数为main();,主线程的处于激活状态,主线程参与了CPU的竞争,程序正常运行了。

进程的建立
函数CreateProcess,原型见P15页。调用的时候需给系统指定一个STARTUPINFO变量,该变量保存一些进程初运行的信息,包括图形窗口的信息等。同时返回一个进程标志信息(PROCESS_INFORMATION),如ID、句柄信息等。
例:
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
char* szCommandLine="notepad";
::CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,NULL,NULL,NULL,&si,&pi);
将打开记事本程序进程,si为运行初信息,pi为返回进程信息。

多线程
以前编程的思想都是单一的,一个程序总是从开始一行跑到最后一行,不管是分支还是循环,总之是从头运行到脚,跟汇编的思想一样,有一个PC指明指令的顺序,这个是容易理解的,因为我们只有一个CPU。但为什么我们现在可以同时打开多个任务,而多个任务却可以同时工作呢?这里的多任务可以是多进程,也可以是一个进程里的多线程,他实际上是把程序分成很多的小块,多个任务的小块都在那里排队,等着CPU运行,CPU运行时又不会按它的意愿一直运行下去,当一定时间过了之后又会把它调出继续排队,如此直到它自己结束。
这里的小块就是线程,Thread,任何线程都有它所属的进程,我觉得这样做的目的是保护彼此之间的权利以及使代码更简单,如果线程能超出进程的范围,那还要进程做什么?线程只代表运行期的方向和时机,而进程确代表整个它运行着的线程的范围和空间。
注意到线程运行的机制,这里需要硬件的支持,书上没提,一个线程的小块(数据结构)里包含着一些必要信息,它们叫线程的上下文,保存着CPU的各寄存器的状态,这样突然中断的一个线程在再一次调入时可以继续上次的运行,如果不用硬件支持,当我们要唤出一个线程的时候,我们又得发一个命令,这个命令本身的信息又会到达CPU,这是很矛盾的事情。而事实上我们只要发一个中断,中断会先保存CPU现块信息,执行中断,再恢复现场,我们可以从现场信息中得到CPU当前状态而不用软件支持。这只是我所想的,具体如何实现的我不清楚。
书上P33页有线程内核对象(Thread Kernel Object)的数据结构,里面有使用计数器UsageCount,用来给系统回收线程的,建立之初为2,打开(调用)一次加1,线程结束时减1,执行CloseHandle()时再减1,如果它为0则系统自动回收。
OpenThread();//打开一个线程,UsageCount+1
CloseHandle();
此外还有暂停次数SuspendCount,它就是标记线程挂起次数的,挂一次加1,唤醒一次减1,当它为0则线程处于激活状态,可参于CPU竞争。
SuspendThread();//挂起一个线程
ResumeThread();//唤醒一个线程
最后还有一个受信标记Signaled
书上只提到当一个线程结束时,受信标记为变为TRUE,运行时永远都是FALSE,至于它用动机我觉得很重要,这关系到线程通信。

关于线程通信,应该从WaitForSingleObject()函数说起。它的作用是让一个线程等待另一个线程的结束。‘等待’是什么意思?如果是简单的用循环‘观察’另一个线程的受信标记,那会不会很浪费CPU资源?我们在做一个无用的循环,这个循环一直在读另一个线程的受信标记,一直在判断比较,然后再读再判断,如果线程比较多,那这将是多大的浪费!书上没提这个函数实现原理的具体细节,我有些疑惑,为此我想到一些东西,如果我是系统设计师我应该怎样做不浪费资源。

我想到的是,我可以给每一个线程分配一个小的关联的数组,用来存放和这个线程相关的其它线程的句柄,运行时是和它们无关的,当运行结束时,把这些和它相关的线程全部ResumeThread();,那如果这样一来,我们可以在WaitForSingleObject()中简单地做两件事:1、关联等待的线程;2、SuspendThread(Myself)。这样等待的线程就被挂起,而不用做无用的循环判断了。
这只是我的想法,但还是有些不解的地方。如书有一个例子P49:
ina main(int argc,char* argv[])
{
  ...
  for(i=0;i<10;i++)
  {  ::WaitForSingleObject(h[i],INFINITE);
     ::CloseHandle(h[i]);
  }
  ...
}
是一个多线程相关的例子,在上面的循环中,如果按我的做法,在第一个循环中,当前main()线程就会被挂起,而只有当h[0]受信后main()线程继续,然后关联h[1]。。。对对对,没错,没有关系,我的做法是可行的,有可能main()是被不断的暂停和挂起,目的是在等待关键时刻进行::CloseHandle(),如果UsageCount==2,也就是线程没有正常退出,那个时候对它进行::CloseHandle()会出错,所以才会进行关联等待它结束。

当我想到这个方法的时候,那如果要实现,对于线程就必须要有一个小的存储空间进行线程间的关联,或者说通信,而事实上也是有的,这个叫TLS,线程局部存储,见P47。于是我更加相信我的方法了。

这是线程间通信的一个小机制,类似于信号量,总而言之,只要让线程间能够通信,我们就能实现线程的同步,以及临界区的使用。

简单说一点吧,这是计算机基础知识。同步也就是相互关联,同样的道理,你只有一个CPU,你不可能同时同步运行两个线程,那么对需要同步运行的线程就是相互等待,A和B同步,首先A运行,B等待,A完成后,B运行,A等待,举个生活中的例子,假设只有一个收银员,那对于顾客进收银台和收银员进行收MONEY这两个动作就需要同步。一同步相对的是异步,就是两个进程的运行情况没有直接关系,谁运行得长或短,完全由系统调度决定,一般情况下线程都是异步的,比如你打开的QQ和你正在看电影用的暴风影音,互不影响。

临界区的例子比较好玩P41,不再多说,对那个例子的理解会更加深刻的认识到线程的运行原理。实现起来需要再引一个临界区对象,事实上是一个线程,比如A,B要对临界区S异步操作,当任一线程进入S时,和临界区对象关联::EnterCriticalSection();,作用是看有没有线程已经在临界区,如果有,挂起,没有则进入,当一个操作临界区的线程退出临界区时,会再一次让临界区对象通知其它和他关联了的想进入临界区的线程::LeaveCriticalSection();,临界区对象本身也是大部分时间挂起,当有线程想进或想出时工作。

OK,到此为止,下一章进入GUI。

阅读(6930) | 评论(5)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册