📄 csdn_文档中心_com样例(一)——《com线程模型》基础篇.htm
字号:
<SCRIPT src="CSDN_文档中心_COM样例(一)——《COM线程模型》基础篇.files/marquee.htm"></SCRIPT>
</MARQUEE></TD>
<TD align=right bgColor=#f3f9fc width=101>
<SCRIPT>document.write("<img src=http://count.csdn.net/count/pageview1.asp?columnid=4&itemid=11 border=0 width=0 height=0>");</SCRIPT>
<B>
<SCRIPT language=JavaScript>
<!--
document.ns = navigator.appName == "Netscape"
tmpDate = new Date();
date = tmpDate.getDate();
month= tmpDate.getMonth() + 1 ;
if(document.ns)
{
year1=tmpDate.getYear()
year= year1.toString().substr(1,2);
}
else
year= tmpDate.getYear();
document.write(year);
document.write(".");
document.write(month);
document.write(".");
document.write(date);
// -->
</SCRIPT>
</B> </TD></TR>
<TR bgColor=#999999>
<TD colSpan=3 height=1></TD></TR></TBODY></TABLE>
<TABLE border=0 width=770>
<TBODY>
<TR>
<TD align=middle bgColor=#fafafa class=td1 vAlign=top width=150><BR>
<SCRIPT
src="CSDN_文档中心_COM样例(一)——《COM线程模型》基础篇.files/microsoft.js"></SCRIPT>
</TD>
<TD align=middle width=620>
<TABLE bgColor=#eeeeee border=0 cellPadding=0 cellSpacing=0 width=600>
<TBODY>
<TR bgColor=#ffffff>
<TD align=middle height=10 width=50></TD>
<TD align=right><A href="http://www.csdn.net/">CSDN</A> - <A
href="http://www.csdn.net/develop/">文档中心</A> - <FONT
color=#003399>Visual C++</FONT> </TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399 height=10><FONT
color=#ffffff>标题</FONT></TD>
<TD><B> COM样例(一)——《COM线程模型》基础篇</B> lop5712(原作)
</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR>
<TR>
<TD align=middle bgColor=#003399><FONT color=#ffffff>关键字</FONT></TD>
<TD width=500> COM 线程 样例 基础</TD></TR>
<TR>
<TD align=middle height=5></TD>
<TD align=middle width=500></TD></TR></TBODY></TABLE><!--文章说明信息结束//-->
<TABLE border=0 width=600>
<TBODY>
<TR>
<TD align=left><BR><FONT
style="FONT-FAMILY: '宋体'; FONT-SIZE: 10pt; LINE-HEIGHT: 150%">
<P align=center><FONT face=楷体_GB2312
size=5><STRONG>COM样例(一)</STRONG></FONT></P>
<P align=right><FONT face=楷体_GB2312 size=4><STRONG>——《<SPAN
lang=EN-US>COM线程模型》基础篇</STRONG></FONT></P>
<P>
我曾经写了一篇《COM线程模型》,受到一些好评,但也有部分人觉得其有些深,希望能提供一些基础点的文章。本系列文章是《COM线程模型》这篇文章的样例,提供一个简单的稍完整的例子以帮助理解前面文章中的内容。本文是此系列的第一篇,讲解《COM线程模型》中出现过的我以为可能需要说明的基础概念,在后继的文章中说明样例的结构,并给出相关各部分代码,以注释作为主要说明手段。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>线程</STRONG></FONT></P>
<P>
线程是具有进程能动性的逻辑概念。所谓能动性是指能使某种东西按某种规则动起来的特性,而所谓的"动"就是前面的"某种东西"的状态的改变。那么进程能动性即使进程按某种规则运行的性质,这里的"某种规则"即是CPU的机器指令的定义。可以简单地将线程看成是操纵机器的工人,而机器就相当于电脑硬件,而这个工人的工作就是代码。工作由老板制定好后,就等着工人去做。没人做的事就和代码一样,没有任何意义。当一个工人去操纵机器进行工作,就相当于一个线程通过CPU执行进程中的代码。毛坯由于人操作机器而改变它的状态进而变成产品,进程因为线程让CPU执行进程中的代码(准确地说应该是CPU对指定数字的反应)而改变它所拥有的内存的状态,进而将计算结果表现出来。<BR>
线程就等同于时间,时间是具有客观世界能动性的逻辑概念,其使客观世界按上帝制订的高深莫测的规律变化其自身的状态。线程则使进程中的内容(包括代码)按CPU的机器指令的定义来改变进程的状态(其内存的数值)。<BR>
应该将线程句柄和线程区分开来。线程句柄是个对象,即一个结构,其具体定义是由操作系统决定的,并一定关联着一个线程。它相当于是一个日志记录,其和线程没有什么关系,只是一个记录其相关线程的一些状态,如线程是否在等待、线程的消息队列等,操作系统利用线程句柄对线程进行操作,如发送线程消息、等待线程结束等。<BR>
由于线程句柄只是一个对象,相当于一条记录,所以即使其关联的线程已经不存在了(线程退出了),其也是有效的。就好像一个人死了,但他的身份证还是存在的(虽然已经没有意义了)。并且即使线程句柄已经通过CloseHandle释放掉了(准确地说只是引用记数减一,因为操作系统在线程未退出时还保留着其一个引用)也并不代表线程结束了,它只是一个结构,记录线程的相关信息,与线程本身并无关系。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>线程安全</STRONG></FONT></P>
<P>
前面说过线程就相当于一个工人,可以两个工人同时操作一台机器,这两个工人就必须分工合作,A工人设置了机器的加工速度为10,然后他还没有开始用10这个加工速度加工零件,B工人就又将加工速度定为了20,如果A不知道,这非常有可能导致事故发生。此时我们就说机器的加工速度的设置不安全——任何人都可以在不发出任何通知的前提下改变它。<BR>
因此就规定这个机器只能由一个工人操作,不准同时有两个工人操作这台机器,此同步手段在编程时是通过程序中只有一个线程来实现的。<BR>
此机器比较先进,可以同时做很多事情,如果只让一个工人操作效率太低。故决定让两个工人操作,但是在设置加工速度时,在设置把手处挂一个牌子,如果A已经设置了加工速度并正在加工零件,就将牌子写有“使用中”字样的一面朝外,用完后再将牌子写有“未使用”的一面朝外。这样就实现了对加工速度设置的同步。此在Windows编程时一般是通过互斥量(Mutex)来实现的。<BR>
因此,当多个线程都需要操作进程中某块内存时,这块内存就和上面的“机器的加工速度”一样,对其的改变需要施加同步手段以防止出现问题。当由于这个原因而出现问题时,此问题一般就被称为线程冲突(Race
Condition)。<BR>
除了上面提到的线程冲突外,还有一种称为死锁(Deadlock)的线程安全问题。比如A工人为了加工Ⅲ零件在等待B提供的Ⅰ零件,而B正好在等待应由A加工提供的Ⅱ零件以装配Ⅰ零件。由于他们之间再没有其他任何人帮助通信或其他通信手段(如等腻了走过去看看),他们都是坐在办公室里等着对方因为零件加工好而欲提供的电话,也就永远都不会等到电话而变成一个死局。<BR>
这种就是死锁,双方互相等待,此属于逻辑错误,并不像上面的线程冲突那样可以通过固定的方法解决,这是设计算法时犯下的错误,当线程较多,相互间关系复杂时是很容易产生的,可以通过断言来帮助检查。<BR>
因此一段代码被称为线程安全的,就是指这段代码不会发生线程冲突或死锁。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>线程切换</STRONG></FONT></P>
<P>
一般的PC都是只有一个CPU,而多个线程的运行使得电脑看起来好像同时在做很多事,此是通过一会做这件事,一会再做另一件事来实现的。即操作系统会频繁地让多个线程各执行一小段时间(被称为时间片),以期望通过足够的频繁而使得其好象在同时执行多个线程。当某个执行中线程执行够了它的时间片后,操作系统就会通过某些操作让另一个线程进入运行,而前者则停止运行,这里的“某些操作”就是所谓的线程切换。其具体操作就是将当前线程的运行环境(Context)保存起来,如将CPU各寄存器的值存到一个TLS(后叙)内存中,然后将欲运行的线程的运行环境,加载到当前CPU硬件以让线程运行,因此线程切换就是线程运行环境的保存和加载。<BR>
在《COM线程模型》中提到线程切换是非常耗时的,而操作系统却又极其频繁地发生线程切换,那么操作系统岂不是非常地效率低下?不是。《COM线程模型》中所谓的线程切换的耗时并不是线程切换本身耗时,而是线程发生运行方式转换(从用户方式转为内核方式),此操作一般会有1000个以上的CPU周期,此即所谓的耗时。<BR>
Windows中的线程可以运行在两种方式下,内核方式(Kernel Mode)和用户方式(User
Mode)。<BR>
内核方式下是可以直接访问物理内存的,而不是进程的虚拟内存空间,此时具有多种特权。此时如果发生错误,可能是整个操作系统挂起(Halt)而不是简单地用任务管理器(或类似软件)就可以搞定的。此一般是内核代码或硬件驱动程序的工作方式。<BR>
用户方式则是普通的运行方式,只能通过进程的虚拟内存空间的映射来访问物理内存,没有特权。<BR>
Windows有三种对象:用户对象(User Object)、GDI对象(GDI Object)和内核对象(Kernel
Object)。内核对象所使用的内存是在内核模式下才可访问的内存。Windows对每种内核对象都提供了一系列的API以对其操作。当调用这类API时,线程必须从用户方式转换成内核方式以操作内核对象所在的内存,这也就导致了前面提过的损耗。<BR>
当我们进行线程同步时,会调用类似WaitForSingleObject之类的等待函数,此时线程会挂起,等待指定的内核对象处于通知状态,此时就会发生上面的损耗。而COM缺省提供的线程同步功能由于其灵活性注定了不能使用用户方式的同步(如原子访问、关键代码段),因此在STA线程和MTA线程之间及各自之间的同步就使用了内核同步对象(如事件、互斥量等)来进行同步,也就导致了上面提到的损耗,也就是NA套间产生的原因及目的。</P>
<P><BR><FONT face=楷体_GB2312 size=4><STRONG>线程局部存储(Thread Local
Storage)</STRONG></FONT></P>
<P>
线程局部存储(TLS)是Windows提供的一种技术,用于将一些内存和一线程关联起来,这样即使同样的代码,不同的线程访问,实际将会访问不同的内存,这和线程的堆栈是一样的道理。<BR>
那为什么不直接使用堆栈还要来个TLS?因为堆栈相当于是一个历史记录,里面的内存数值与调用顺序有着密切关系。如果希望多个函数间共享一块内存,应该使用全局变量,但是由于又想线程相关,决定将内存分配在栈上(不过这不重要),则在执行函数时必须准确知道内存分配在栈上的什么位置(也就是地址),而这个位置又需要通过另一个全局变量来进行函数间传递,因此又需要栈上一个内存,又……。这是一个死循环,这也就是为什么有TLS的存在。<BR>
TLS共提供了4个API,分别为TlsAlloc、TlsSetValue、TlsGetValue和TlsFree。调用TlsAlloc将得到一个cookie,是一个DWORD值,是个序号。然后分配一块内存,将内存的地址通过TlsSetValue和前面得到的cookie保存起来,然后在适当的时候调用TlsGetValue得到记录的地址,程序不再使用TLS的时候调用TlsFree释放前面的cookie即可。<BR>
上面的关键就在于不同的线程调用TlsGetValue,即使提供同样的cookie,返回的也不是同一个值。同样,不同的线程调用TlsSetValue,即使同样的cookie,却不是互相干涉,并且在TlsGetValude时能正确返回。故上面的cookie可以是个全局变量。如:<BR><BR><FONT
color=blue>#include</FONT> <stdio.h><BR><FONT
color=blue>#include</FONT> <windows.h><BR><BR><FONT
color=green>// 出于样例,不做任何错误检查及处理</FONT><BR>DWORD g_Cookie = <SPAN
style="COLOR: blue">static_cast</SPAN>< DWORD >( -1
);<BR>DWORD g_Index = 1;<BR><SPAN
style="COLOR: blue">struct</SPAN> ABCD { <SPAN
style="COLOR: blue">long</SPAN> a; };<BR><SPAN
style="COLOR: blue">void</SPAN> CBA()<BR>{<BR>
<SPAN style="COLOR: blue">reinterpret_cast</SPAN>< ABCD* >(
TlsGetValue( g_Cookie ) )->a = g_Index++;<BR>
Sleep( rand() % 1000 );<BR>}<BR>DWORD WINAPI AB( LPVOID
)<BR>{<BR> ABCD *pTemp = <SPAN
style="COLOR: blue">new</SPAN> ABCD;<BR>
TlsSetValue( g_Cookie, pTemp );<BR>
CBA();<BR> printf( "%d\n", pTemp->a );
<BR> <SPAN style="COLOR: blue">delete</SPAN>
pTemp;<BR> <SPAN style="COLOR: blue">return</SPAN>
0;<BR>}<BR><SPAN style="COLOR: blue">void</SPAN>
main()<BR>{<BR> g_Cookie =
TlsAlloc();<BR><BR> DWORD id = <SPAN
style="COLOR: blue">static_cast</SPAN>< DWORD >( -1
);<BR> HANDLE hThreads[4]; <BR>
<SPAN style="COLOR: blue">for</SPAN>( <SPAN
style="COLOR: blue">unsigned</SPAN> <SPAN
style="COLOR: blue">long</SPAN> i = 0; i < 4; ++i
)<BR> hThreads[ i ] =
CreateThread( NULL, 0, AB, NULL, 0, &id
);<BR><BR> WaitForMultipleObjects( 4, hThreads,
TRUE, INFINITE );<BR><BR> <SPAN
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -