﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-小猪和小叮当的小爱屋-文章分类-Delphi</title><link>http://www.cnitblog.com/jack29/category/3411.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 30 Sep 2011 04:05:35 GMT</lastBuildDate><pubDate>Fri, 30 Sep 2011 04:05:35 GMT</pubDate><ttl>60</ttl><item><title>Delphi threading by example</title><link>http://www.cnitblog.com/jack29/articles/14683.html</link><dc:creator>小猪和小叮当的小爱屋</dc:creator><author>小猪和小叮当的小爱屋</author><pubDate>Fri, 04 Aug 2006 02:16:00 GMT</pubDate><guid>http://www.cnitblog.com/jack29/articles/14683.html</guid><wfw:comment>http://www.cnitblog.com/jack29/comments/14683.html</wfw:comment><comments>http://www.cnitblog.com/jack29/articles/14683.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/jack29/comments/commentRss/14683.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jack29/services/trackbacks/14683.html</trackback:ping><description><![CDATA[
		<span id="_ctl2_lblContent">
				<font face="Courier New" size="2"> </font>
				<h1>
						<font face="Courier New" size="2">Delphi threading by example</font>
				</h1>
				<h2>
						<font face="Courier New" size="2">An example with the Windows API</font>
				</h2>
				<p>
						<a class="" title="杰克逊经典机械舞" href="http://movie.825.cn:1006/ha/jbjw/20060615/杰克逊经典机械舞.wmv" target="">MJ</a>
				</p>
				<p>
						<i>
								<font face="Courier New" size="2">By Wim De Cleen, Software Engineer with Electromatic Sint-Niklaas NV (Belgium)</font>
						</i>
				</p>
				<p>
						<font face="Courier New" size="2">Threads are somewhat overwhelming until you have written some threading code and gotten experience with their ins and outs. This article will introduce you to the art of threading and synchronization. I use the Windows API calls to show you how it really works -- the hard truth. I wrote an example that searches for a specific string in multiple  files. As the threads search, real-time synchronized information is sent to the main form. Of course, the data could also be sendtto another thread or application. The more you think about it, the more you see the power of threading. </font>
				</p>
				<h2>
						<font face="Courier New" size="2">Why synchronize? </font>
				</h2>
				<p>
						<font face="Courier New" size="2">When a thread is running, it runs on its own without wondering what other threads are doing. That is why one thread can calculate a sum of two numbers -- let's say <i>a </i>and <i>b, </i>while another thread comes to life and changes the value of <i>a. </i>This is not what we want! So we need a shell around the global variables so that no other thread can access them. Then, when the calculations are done, we might remove the shell to give other threads in the system access to the data once more. </font>
				</p>
				<p>
						<font face="Courier New" size="2">The easiest way to construct such a shell is to use <i>critical sections. </i>We will use these as a start. </font>
				</p>
				<p>
						<font face="Courier New" size="2">(Incidentally, synchronization bugs like this can bite you when you are using VCL components, which are generally not thread-safe. When you start updating controls from different threads everything may seem fine...but the bug will present itself some time in the future. Then it is up to you to find the synchronization bug. Believe me, this can take a while. Debugging threads is really frustrating! </font>
				</p>
				<h2>
						<font face="Courier New" size="2">Getting started </font>
				</h2>
				<p>
						<font face="Courier New" size="2">Delphi's TThread class has a method called synchronize, and if it did everything we wanted then there would be no need for this article. TThread lets you specify a method that needs to be synchronized with the main thread (the thread of the VCL). This is handy if you can afford to spend time waiting for synchronization, but in systems where milliseconds are crucial you can't use this method: It halts the execution of the thread during synchronization. </font>
				</p>
				<p>
						<font face="Courier New" size="2">My alternative method is based on messages. With the function PostMessage you can deliver messages to any thread in the system without waiting for the result. The message is just placed onto the message queue of the receiving thread and stays there until the receiving thread comes to life. At that time the message is handled. All that time your thread continues to run. </font>
				</p>
				<h2>
						<font face="Courier New" size="2">Critical sections </font>
				</h2>
				<p>
						<font face="Courier New" size="2">Critical sections are the simplest synchronizers possible. They are fast because they can be used only within the same process. A critical section can be used to protect global variables but you must perform the coordination yourself. You can't bind a variable to a critical section programmatically; it must be done logically. </font>
				</p>
				<p>
						<font face="Courier New" size="2">Let's say we want to protect any access to the variables <i>a </i>and <i>b. </i>Here's how to do it with the critical sections approach: </font>
				</p>
				<pre>
						<font size="2">//global variables
<b>var</b> CriticalSection: TRTLCriticalSection;
    a, b: integer; </font>
				</pre>
				<pre>
						<font size="2">//before the threads starts
InitializeCriticalSection(CriticalSection); </font>
				</pre>
				<pre>
						<font size="2">//in the thread 
EnterCriticalSection(CriticalSection); 
//From now on, you can safely make 
//changes to the variables. 
  inc(a); 
  inc(b); 
//End of safe block 
LeaveCriticalSection(CriticalSection); </font>
				</pre>
				<p>
						<font face="Courier New" size="2">See? Nothing to it. This approach lets you create multiple threads without worrying about the effect on global variables. </font>
				</p>
				<h2>
						<font face="Courier New" size="2">Starting the thread </font>
				</h2>
				<p>
						<font face="Courier New" size="2">When creating a thread without the TThread class, always use the BeginThread function from the SysUtils unit. It is specifically written to use Pascal functions and it encapsulates the CreateThread winapi call. </font>
				</p>
				<p>
						<font face="Courier New" size="2">Let's take a look at the declaration and step through the parameters. </font>
				</p>
				<pre>
						<font size="2">BeginThread(SecurityAttributes: Pointer;
	StackSize: LongWord; 
	ThreadFunc: TThreadFunc;
        Parameter: Pointer; 
	CreationFlags: LongWord; 
	var ThreadId: LongWord): Integer;  </font>
				</pre>
				<ul>
						<li>
								<font face="Courier New" size="2">SecurityAttributes: a pointer to a security record, used only in windows NT, fill in nil </font>
						</li>
						<li>
								<font face="Courier New" size="2">StackSize: the initial stack size of the thread. The default value is 1MB. If you think this is too small fill in the desired size, otherwise if not fill in 0. </font>
						</li>
						<li>
								<font face="Courier New" size="2">ThreadFunc: This is the function that will be executed while the thread is running. This is mostly a function with a while loop inside. The prototype is function(Parameter: Pointer): Integer </font>
						</li>
						<li>
								<font face="Courier New" size="2">Parameter: This is a pointer to a parameter, which can be anything you like. It will be passed to the thread function as the parameter pointer. </font>
						</li>
						<li>
								<font face="Courier New" size="2">CreationFlags: This flag determines whether the thread starts immediately or it is suspended until ResumeThread is called. Use CREATE_SUSPENDED for suspended and 0 for an immediate start. </font>
						</li>
						<li>
								<font face="Courier New" size="2">ThreadID: This is a var parameter that will return the ThreadID of the thread. </font>
						</li>
				</ul>
				<h2>
						<font face="Courier New" size="2">Handling the messages </font>
				</h2>
				<p>
						<font face="Courier New" size="2">Handling messages sent to a form is simple in Delphi. You just have to declare a procedure with the following prototype: procedure(var Message:TMessage). This procedure must be declared as a method of your form class. By using the message directive you bind a message to the function. In the form declaration it looks like this: </font>
				</p>
				<pre>
						<font size="2">
								<b>const</b> 
  TH_MESSAGE = WM_USER + 1;
<b>type</b> 
  Form1 = <b>class</b>(TForm) 
  <b>private</b><b>procedure</b> HandleMessage(<b>var</b> Message: 
	TMessage); <b>message</b> TH_MESSAGE; 
  <b>public</b><b>end</b>; </font>
				</pre>
				<p>
						<font face="Courier New" size="2">From now on every message sent to the form with the message parameter TH_MESSAGE will invoke the HandleMessage procedure. </font>
				</p>
				<p>
						<font face="Courier New" size="2">We can send a message with the PostMessage function which looks like the following" </font>
				</p>
				<pre>
						<font size="2">PostMessage (Handle: HWND; Message: Cardinal; WParam: integer; LParam:integer) </font>
				</pre>
				<ul>
						<li>
								<font face="Courier New" size="2">Handle: The handle of the receiving thread or receiving form. </font>
						</li>
						<li>
								<font face="Courier New" size="2">Message: The messageconstant. </font>
						</li>
						<li>
								<font face="Courier New" size="2">WParam: An optional parameter. </font>
						</li>
						<li>
								<font face="Courier New" size="2">LParam: An optional parameter. </font>
						</li>
				</ul>
				<h2>
						<font face="Courier New" size="2">Putting things together. </font>
				</h2>
				<p>
						<font face="Courier New" size="2">The example program uses messages to deliver information to the main form from within threads. </font>
				</p>
				<p>
						<font face="Courier New" size="2">First I declared the message constants and the submessage constants. Submessages will be passed through the LParam. </font>
				</p>
				<pre>
						<font size="2">
								<b>const</b> 
  TH_MESSAGE = WM_USER + 1; //Thread message 
  TH_FINISHED = 1;          //Thread SubMessage 
			    //End of thread 
			    //WParam = ThreadID 
  TH_NEWFILE = 2;           //Thread Submessage
			    //Started new file 
  TH_FOUND = 3;             //Thread Submessage 
			    //Found search string in File 
  TH_ERROR = 4;             //Thread SubMessage
			    //Error -- WParam = Error </font>
				</pre>
				<p>
						<font face="Courier New" size="2">Then the information records, which will be passed through WParam:</font>
				</p>
				<pre>
						<font size="2">
								<b>type</b> 
//Record for found items, will occur when 
//LParam = TH_FOUND, WParam will be 
//PFoundRecord 
PFoundRecord = ^TFoundRecord; 
TFoundRecord = <b>record</b> 
	ThreadID: Cardinal; 
        Filename: <b>string</b>; 
        Position: Cardinal; 
        <b>end</b>; 

//Record for new files, will occur when 
//LParam = TH_NEWFILE, WParam will be 
//PNewFileRecord 
PNewFileRecord = ^TNewFileRecord; 
TNewFileRecord = <b>record</b> 
        ThreadID: Cardinal; 
        Filename: <b>string</b>;    
        <b>end</b>; </font>
				</pre>
				<p>
						<font face="Courier New" size="2">Since we need some information about the threads we declare an info record for them: </font>
				</p>
				<pre>
						<font size="2">//Record to hold the information from one thread 
TThreadInfo = <b>record</b> 
        Active: Boolean; 
        ThreadHandle: integer; 
        ThreadId: Cardinal; 
        CurrentFile: <b>string</b>;
        <b>end</b>; </font>
				</pre>
				<p>
						<font face="Courier New" size="2">Finally we declare the TMainForm class, which contains the message handler:</font>
				</p>
				<pre>
						<font size="2">//The Main form of the application 
TMainForm = <b>class</b>(TForm) 
  btSearch: TButton;    
  Memo: TMemo; 
  OpenDialog: TOpenDialog; 
  edSearch: TEdit; 
  StringGrid: TStringGrid;    
  Label1: TLabel; 
  <b>procedure</b> btSearchClick(Sender: TObject); 
  <b>procedure</b> FormCreate(Sender: TObject); 
  <b>procedure</b> FormDestroy(Sender: TObject); 
  <b>procedure</b> edSearchChange(Sender: TObject); 
<b>private</b> 
    ThreadInfo: <b>array</b>[0..4] <b>of</b> TThreadInfo; 
    //Holds the information of the threads 
    <b>procedure</b> ThreadMessage(var Message: TMessage);
	<b>message</b> TH_MESSAGE; //MessageHandler 
    <b>function</b> ThreadIDToIndex(ThreadID: Cardinal): 
	integer; 
<b>public</b><b>end</b>; </font>
				</pre>
				<p>
						<font face="Courier New" size="2">In the implementation section we declare our global variables, the critical section, a search string, and the list of files we will be searching. </font>
				</p>
				<pre>
						<font size="2">var CriticalSection: TRTLCriticalSection; 
	//Critical section protects the filelist 
    FileList: TStringList;         
	//List of filenames to be searched 
    SearchString: string;              
	//String to be searched in every file </font>
				</pre>
				<p>
						<font face="Courier New" size="2">Then follows the thread function, this is the function that delivers all the work for the thread. It will be passed to the begin thread function. </font>
				</p>
				<pre>
						<font size="2">
								<b>function</b> FindInFile(data: Pointer): Integer; 
<b>var</b> FileStream: TFileStream;    
    Ch: char; 
    Current,Len: Integer; 
    FoundRecord: PFoundRecord; 
    NewFileRecord: PNewFileRecord; 
    Filename: <b>string</b>; 
    Search: <b>string</b>; 
    FilesDone: Boolean; 
<b>begin</b>    
  Result:= 0; 
  FilesDone:= False; 
  <b>while</b><b>not</b> FilesDone <b>do</b><b>begin</b> 
    Current:= 1; 
    EnterCriticalSection(CriticalSection); 
	//Try to catch the critical section 
    Search:= SearchString; 
	//Access the shared variables 
    	//Are there still files available 
    <b>if</b> FileList.Count = 0 <b>then</b><b>begin</b> 
      //Leave the critical section 
      //when there are no files left 
      LeaveCriticalSection(CriticalSection);    
      //Leave the while loop 
      break; 
    <b>end</b><b>else</b><b>begin</b> 
      //Read the filename 
      Filename:= FileList.Strings[0]; 
      //Delete the file from the list 
      FileList.Delete(0); 
      //Leave the critical section 
      LeaveCriticalSection(CriticalSection); 
      
      //Inform MainForm of New File 
      New(NewFileRecord); 
      NewFileRecord^.Filename:= Filename; 
      NewFileRecord^.ThreadID:= GetCurrentThreadID; 
      PostMessage(MainForm.Handle, 
	TH_MESSAGE, TH_NEWFILE,    
	Integer(NewFileRecord)); 
      
      Len:= Length(Search); 
      <b>try</b> 
        FileStream:= TFileStream.Create(
	  Filename, fmOpenRead or fmShareExclusive); 
      <b>except</b> 
        PostMessage(MainForm.Handle, TH_MESSAGE, 
	  TH_ERROR, ERROR_COULD_NOT_OPEN_FILE); 
        continue; 
      <b>end</b>; 
      
      //The search algorithm, pretty simple, 
      //the example is not about searching 
      <b>while</b> FileStream.Read(Ch,1)= 1 <b>do</b><b>begin</b><b>If</b> Ch = Search[Current] <b>then</b><b>begin</b> 
          Inc(Current); 
          <b>if</b> Current &gt; Len <b>then</b><b>begin</b> 
            //Found the search string, 
	    //inform MainForm of our success 
            New(FoundRecord);    
            FoundRecord^.Filename:= Filename; 
            FoundRecord^.Position:= FileStream.Position;    
            FoundRecord^.ThreadID:= GetCurrentThreadID; 
            PostMessage(MainForm.Handle, 
		TH_MESSAGE, TH_FOUND, 
		Integer(FoundRecord)); 
          <b>end</b>; 
        <b>end</b><b>else</b><b>begin</b> 
          FileStream.Position:=    
	    FileStream.Position - (Current - 1); 
          Current:= 1; 
        <b>end</b>; 
      <b>end</b>; 
      FileStream.Free;    
    <b>end</b>; 
  <b>end</b>; 
  //All done inform MainForm of ending 
  PostMessage(MainForm.Handle,    
	TH_MESSAGE, TH_FINISHED, GetCurrentThreadID); 
<b>end</b>;</font>
				</pre>
				<p>
						<font face="Courier New" size="2">Another important procedure is the message handler, which -- drum roll, please -- handles the messages. It also frees the record pointers. </font>
				</p>
				<pre>
						<font size="2">
								<b>procedure</b> TMainForm.ThreadMessage(<b>var</b> Message: TMessage); 
<b>var</b> FoundRecord: PFoundRecord; 
    NewFileRecord: PNewFileRecord; 
    ThreadIndex: integer; 
    Counter: integer; 
    Ended: boolean; 
<b>begin</b><b>case</b> Message.WParam <b>of</b>   
  TH_FINISHED: 
    <b>begin</b>    
      ThreadIndex:= ThreadIDToIndex(Message.LParam); 
      <b>if</b> ThreadIndex = -1 <b>then</b> Exit;
	//Invalid threadID should never appear 
      CloseHandle(ThreadInfo[ThreadIndex].ThreadHandle);
	//Free the thread memoryspace 
      StringGrid.Cells[3,ThreadIndex+1]:= 'False';
	//Update the stringgrid 
      Threadinfo[ThreadIndex].Active:= False;
	//Update the ThreadInfo array 
      Ended:= True; 
      <b>for</b> counter:= 0 <b>to</b> 4 <b>do</b><b>if</b> ThreadInfo[ThreadIndex].Active <b>then</b><b>begin</b> 
          Ended:= False; 
          break; 
      <b>  end</b>; 
      <b>if</b> Ended <b>then</b> btSearch.Enabled:= True; 
    <b>end</b>; 
  TH_NEWFILE: 
    <b>begin</b> 
      NewFileRecord:= PNewFileRecord(Message.LParam);    
      ThreadIndex:= ThreadIDToIndex
	(NewFileRecord^.ThreadID); 
      <b>if</b> ThreadIndex = -1 <b>then</b> Exit;
	//Invalid threadID should never appear 
      StringGrid.Cells[2,ThreadIndex+1]:= 
	NewFileRecord^.Filename; //Update StringGrid 
      ThreadInfo[ThreadIndex].CurrentFile:= 
	NewFileRecord^.Filename; //Update ThreadInfo 
      Dispose(NewFileRecord);
	//All information is used now free the pointer 
    <b>end</b>; 
  TH_FOUND: 
    <b>begin</b> 
      FoundRecord:= PFoundRecord(Message.LParam); 
      ThreadIndex:= ThreadIDToIndex(FoundRecord^.ThreadID); 
      if ThreadIndex = -1 then Exit;
	//Invalid threadID should never appear 
      Memo.Lines.Add(FoundRecord^.Filename + ' Position:' 
        + IntToStr(FoundRecord^.Position)); 
      Dispose(FoundRecord);
	//All information is used now free the pointer 
    <b>end</b>; 
  TH_ERROR: 
    <b>begin</b> 
      ThreadIndex:= ThreadIDToIndex(Message.LParam); 
      <b>if</b> ThreadIndex = -1 <b>then</b> Exit;
	//Invalid threadID should never appear 
      Memo.Lines.Add('Error: Could not open file '    
        + ThreadInfo[ThreadIndex].CurrentFile); 
    <b>end</b>; 
  <b>end</b>; 
<b>end</b>; </font>
				</pre>
				<p>
						<font face="Courier New" size="2">You don't need to worry about pasting the functions and classes together. I have included the complete </font>
						<a href="http://community.borland.com/article/images/22411/ThreadExample.zip">
								<font face="Courier New" color="#191970" size="2">example</font>
						</a>
						<font face="Courier New" size="2">in a Zip file so you can see it working right away. </font>
				</p>
				<p>
						<font face="Courier New" size="2">This is not a complete explanation of threading. It's barely a beginning. But I hope it will serve as a starting point for your own exploration. I wish I had had an example to guide me when I started working with threads! </font>
				</p>
		</span>
<img src ="http://www.cnitblog.com/jack29/aggbug/14683.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jack29/" target="_blank">小猪和小叮当的小爱屋</a> 2006-08-04 10:16 <a href="http://www.cnitblog.com/jack29/articles/14683.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TThread</title><link>http://www.cnitblog.com/jack29/articles/14682.html</link><dc:creator>小猪和小叮当的小爱屋</dc:creator><author>小猪和小叮当的小爱屋</author><pubDate>Fri, 04 Aug 2006 02:15:00 GMT</pubDate><guid>http://www.cnitblog.com/jack29/articles/14682.html</guid><wfw:comment>http://www.cnitblog.com/jack29/comments/14682.html</wfw:comment><comments>http://www.cnitblog.com/jack29/articles/14682.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jack29/comments/commentRss/14682.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jack29/services/trackbacks/14682.html</trackback:ping><description><![CDATA[
		<font style="BACKGROUND-COLOR: #ffffff" face="Courier New" size="2">Delphi中有一个线程类TThread是用来实现多线程编程的，这个绝大多数Delphi书藉都有说到，但基本上都是对TThread类的几个成员作一简单介绍，再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编程的全部，我写此文的目的在于对此作一个补充。<br />线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程，即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时，就是所谓的“多线程”。<br />那么这个所谓的“一段代码”是如何定义的呢？其实就是一个函数或过程（对Delphi而言）。<br />如果用Windows API来创建线程的话，是通过一个叫做CreateThread的API函数来实现的，它的定义为：<br /><br />HANDLE CreateThread(<br />LPSECURITY_ATTRIBUTES lpThreadAttributes, <br />DWORD dwStackSize, <br />LPTHREAD_START_ROUTINE lpStartAddress, <br />LPVOID lpParameter, <br />DWORD dwCreationFlags, <br />LPDWORD lpThreadId <br />);<br /><br />其各参数如它们的名称所说，分别是：线程属性（用于在NT下进行线程的安全属性设置，在9X下无效），堆栈大小，起始地址，参数，创建标志（用于设置线程创建时的状态），线程ID，最后返回线程Handle。其中的起始地址就是线程函数的入口，直至线程函数结束，线程也就结束了。<br />整个线程的执行过程如下图所示：<br /><br />因为CreateThread参数很多，而且是Windows的API，所以在C Runtime Library里提供了一个通用的线程函数（理论上可以在任何支持线程的OS中使用）：<br />unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);<br /><br />Delphi也提供了一个相同功能的类似函数：<br />function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord): Integer;<br /><br />这三个函数的功能是基本相同的，它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的最大不同在于，线程函数一启动，这三个线程启动函数就返回了，主线程继续向下执行，而线程函数在一个独立的线程中执行，它要执行多久，什么时候返回，主线程是不管也不知道的。<br />正常情况下，线程函数返回后，线程就终止了。但也有其它方式：<br /><br />Windows API：<br />VOID ExitThread( DWORD dwExitCode );<br />C Runtime Library：<br />void _endthread(void);<br />Delphi Runtime Library：<br />procedure EndThread(ExitCode: Integer);<br /><br />为了记录一些必要的线程数据（状态/属性等），OS会为线程创建一个内部Object，如在Windows中那个Handle便是这个内部Object的Handle，所以在线程结束的时候还应该释放这个Object。<br /><br />虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了，但是还是需要进行较多的细节处理，为此Delphi在Classes单元中对线程作了一个较好的封装，这就是VCL的线程类：TThread<br />使用这个类也很简单，大多数的Delphi书籍都有说，基本用法是：先从TThread派生一个自己的线程类（因为TThread是一个抽象类，不能生成实例），然后是Override抽象方法：Execute（这就是线程函数，也就是在线程中执行的代码部分），如果需要用到可视VCL对象，还需要通过Synchronize过程进行。关于之方面的具体细节，这里不再赘述，请参考相关书籍。<br />本文接下来要讨论的是TThread类是如何对线程进行封装的，也就是深入研究一下TThread类的实现。因为只是真正地了解了它，才更好地使用它。<br />下面是DELPHI7中TThread类的声明（本文只讨论在Windows平台下的实现，所以去掉了所有有关Linux平台部分的代码）：<br /><br />TThread = class<br /><br />private<br />FHandle: THandle;<br />FThreadID: THandle;<br />FCreateSuspended: Boolean;<br />FTerminated: Boolean;<br />FSuspended: Boolean;<br />FFreeOnTerminate: Boolean;<br />FFinished: Boolean;<br />FReturnValue: Integer;<br />FOnTerminate: TNotifyEvent;<br />FSynchronize: TSynchronizeRecord;<br />FFatalException: TObject;<br />procedure CallOnTerminate;<br />class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;<br />function GetPriority: TThreadPriority;<br />procedure SetPriority(Value: TThreadPriority);<br />procedure SetSuspended(Value: Boolean);<br /><br />protected<br />procedure CheckThreadError(ErrCode: Integer); overload;<br />procedure CheckThreadError(Success: Boolean); overload;<br />procedure DoTerminate; virtual;<br />procedure Execute; virtual; abstract;<br />procedure Synchronize(Method: TThreadMethod); overload;<br />property ReturnValue: Integer read FReturnValue write FReturnValue;<br />property Terminated: Boolean read FTerminated;<br /><br />public<br />constructor Create(CreateSuspended: Boolean);<br />destructor Destroy; override;<br />procedure AfterConstruction; override;<br />procedure Resume;<br />procedure Suspend;<br />procedure Terminate;<br />function WaitFor: LongWord;<br />class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;<br />class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);<br />property FatalException: TObject read FFatalException;<br />property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;<br />property Handle: THandle read FHandle;<br />property Priority: TThreadPriority read GetPriority write SetPriority;<br />property Suspended: Boolean read FSuspended write SetSuspended;<br />property ThreadID: THandle read FThreadID;<br />property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;<br />end;<br /><br />TThread类在Delphi的RTL里算是比较简单的类，类成员也不多，类属性都很简单明白，本文将只对几个比较重要的类成员方法和唯一的事件：OnTerminate作详细分析。<br />首先就是构造函数：<br /><br />constructor TThread.Create(CreateSuspended: Boolean);<br />begin<br />inherited Create;<br />AddThread;<br />FSuspended := CreateSuspended;<br />FCreateSuspended := CreateSuspended;<br />FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);<br />if FHandle = 0 then<br />raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);<br /><br />end;<br /><br />虽然这个构造函数没有多少代码，但却可以算是最重要的一个成员，因为线程就是在这里被创建的。<br />在通过Inherited调用TObject.Create后，第一句就是调用一个过程：AddThread，其源码如下：<br /><br />procedure AddThread;<br />begin<br />InterlockedIncrement(ThreadCount);<br />end;<br /><br />同样有一个对应的RemoveThread：<br />procedure RemoveThread;<br />begin<br />InterlockedDecrement(ThreadCount);<br />end;<br /><br />它们的功能很简单，就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程，而是用了InterlockedIncrement/InterlockedDecrement这一对过程，它们实现的功能完全一样，都是对变量加一或减一。但它们有一个最大的区别，那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证执行结果正确，而Inc/Dec不能。或者按操作系统理论中的术语来说，这是一对“原语”操作。<br />以加一为例来说明二者实现细节上的不同：<br />一般来说，对内存数据加一的操作分解以后有三个步骤：<br />1、从内存中读出数据<br />2、数据加一<br />3、存入内存<br />现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况：<br />1、线程A从内存中读出数据（假设为3） <br />2、线程B从内存中读出数据（也是3）<br />3、线程A对数据加一（现在是4）<br />4、线程B对数据加一（现在也是4）<br />5、线程A将数据存入内存（现在内存中的数据是4）<br />6、线程B也将数据存入内存（现在内存中的数据还是4，但两个线程都对它加了一，应该是5才对，所以这里出现了错误的结果）<br />而用InterlockIncrement过程则没有这个问题，因为所谓“原语”是一种不可中断的操作，即操作系统能保证在一个“原语”执行完毕前不会进行线程切换。所以在上面那个例子中，只有当线程A执行完将数据存入内存后，线程B才可以开始从中取数并进行加一操作，这样就保证了即使是在多线程情况下，结果也一定会是正确的。<br />前面那个例子也说明一种“线程访问冲突”的情况，这也就是为什么线程之间需要“同步”（Synchronize），关于这个，在后面说到同步时还会再详细讨论。<br />说到同步，有一个题外话：加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议，个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”，而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中，Synchronize的意思有两个：一个是传统意义上的同步（To occur at the same time），另一个是“协调一致”（To operate in unison）。在“线程同步”中的Synchronize一词应该是指后面一种意思，即“保证多个线程在访问同一数据时，保持协调一致，避免出错”。不过像这样译得不准的词在IT业还有很多，既然已经是约定俗成了，本文也将继续沿用，只是在这里说明一下，因为软件开发是一项细致的工作，该弄清楚的，绝不能含糊。<br />扯远了，回到TThread的构造函数上，接下来最重要就是这句了：<br /><br />FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);<br /><br />这里就用到了前面说到的Delphi RTL函数BeginThread，它有很多参数，关键的是第三、四两个参数。第三个参数就是前面说到的线程函数，即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数，在这里就是创建的线程对象（即Self）。其它的参数中，第五个是用于设置线程在创建后即挂起，不立即执行（启动线程的工作是在AfterConstruction中根据CreateSuspended标志来决定的），第六个是返回线程ID。<br />现在来看TThread的核心：线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员，而是一个全局函数（因为BeginThread过程的参数约定只能用全局函数）。下面是它的代码：<br /><br />function ThreadProc(Thread: TThread): Integer;<br />var<br />FreeThread: Boolean;<br />begin<br />try<br />if not Thread.Terminated then<br />try<br />Thread.Execute;<br />except<br />Thread.FFatalException := AcquireExceptionObject;<br />end;<br />finally<br />FreeThread := Thread.FFreeOnTerminate;<br />Result := Thread.FReturnValue;<br />Thread.DoTerminate;<br />Thread.FFinished := True;<br />SignalSyncEvent;<br />if FreeThread then Thread.Free;<br />EndThread(Result);<br />end;<br />end;<br /><br />虽然也没有多少代码，但却是整个TThread中最重要的部分，因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明：<br />首先判断线程类的Terminated标志，如果未被标志为终止，则调用线程类的Execute方法执行线程代码，因为TThread是抽象类，Execute方法是抽象方法，所以本质上是执行派生类中的Execute代码。<br />所以说，Execute就是线程类中的线程函数，所有在Execute中的代码都需要当作线程代码来考虑，如防止访问冲突等。<br />如果Execute发生异常，则通过AcquireExceptionObject取得异常对象，并存入线程类的FFatalException成员中。<br />最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置，然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。<br />DoTerminate方法的代码如下：<br /><br />procedure TThread.DoTerminate;<br />begin<br />if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);<br />end;<br /><br />很简单，就是通过Synchronize来调用CallOnTerminate方法，而CallOnTerminate方法的代码如下，就是简单地调用OnTerminate事件：<br /><br />procedure TThread.CallOnTerminate;<br />begin<br />if Assigned(FOnTerminate) then FOnTerminate(Self);<br />end;<br /><br />因为OnTerminate事件是在Synchronize中执行的，所以本质上它并不是线程代码，而是主线程代码（具体见后面对Synchronize的分析）。<br />执行完OnTerminate后，将线程类的FFinished标志设置为True。<br />接下来执行SignalSyncEvent过程，其代码如下：<br /><br />procedure SignalSyncEvent;<br />begin<br />SetEvent(SyncEvent);<br />end;<br /><br />也很简单，就是设置一下一个全局Event：SyncEvent，关于Event的使用，本文将在后文详述，而SyncEvent的用途将在WaitFor过程中说明。<br />然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类，在线程类释放时，还有一些些操作，详见接下来的析构函数实现。<br />最后调用EndThread结束线程，返回线程返回值。<br />至此，线程完全结束。<br />说完构造函数，再来看析构函数：<br /><br />destructor TThread.Destroy;<br />begin<br />if (FThreadID &lt;&gt; 0) and not FFinished then<br />begin<br />Terminate;<br />if FCreateSuspended then<br />Resume;<br />WaitFor;<br />end;<br />if FHandle &lt;&gt; 0 then CloseHandle(FHandle);<br />inherited Destroy;<br />FFatalException.Free;<br />RemoveThread;<br />end;<br /><br />在线程对象被释放前，首先要检查线程是否还在执行中，如果线程还在执行中（线程ID不为0，并且线程结束标志未设置），则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志，如下面的代码：<br /><br />procedure TThread.Terminate;<br />begin<br />FTerminated := True;<br />end;<br /><br />所以线程仍然必须继续执行到正常结束后才行，而不是立即终止线程，这一点要注意。<br />在这里说一点题外话：很多人都问过我，如何才能“立即”终止线程（当然是指用TThread创建的线程）。结果当然是不行！终止线程的唯一办法就是让Execute方法执行完毕，所以一般来说，要让你的线程能够尽快终止，必须在Execute方法中在较短的时间内不断地检查Terminated标志，以便能及时地退出。这是设计线程代码的一个很重要的原则！<br />当然如果你一定要能“立即”退出线程，那么TThread类不是一个好的选择，因为如果用API强制终止线程的话，最终会导致TThread线程对象不能被正确释放，在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创建线程。<br />如果线程处于启动挂起状态，则将线程转入运行状态，然后调用WaitFor进行等待，其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现，将放到后面说明。<br />线程结束后，关闭线程Handle（正常线程创建的情况下Handle都是存在的），释放操作系统创建的线程对象。<br />然后调用TObject.Destroy释放本对象，并释放已经捕获的异常对象，最后调用RemoveThread减小进程的线程数。<br />其它关于Suspend/Resume及线程优先级设置等方面，不是本文的重点，不再赘述。下面要讨论的是本文的另两个重点：Synchronize和WaitFor。<br />但是在介绍这两个函数之前，需要先介绍另外两个线程同步技术：事件和临界区。<br />事件（Event）与Delphi中的事件有所不同。从本质上说，Event其实相当于一个全局的布尔变量。它有两个赋值操作：Set和Reset，相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上，是三个API函数：SetEvent、ResetEvent、WaitForSingleObject（实现WaitFor功能的API还有几个，这是最简单的一个）。<br />这三个都是原语，所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了，现在来说一下WaitFor的功能：<br />WaitFor的功能是检查Event的状态是否是Set状态（相当于True），如果是则立即返回，如果不是，则等待它变为Set状态，在等待期间，调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置，如果此参数为0，则不等待，立即返回Event的状态，如果是INFINITE则无限等待，直到Set状态发生，若是一个有限的数值，则等待相应的毫秒数后返回Event的状态。<br />当Event从Reset状态向Set状态转换时，唤醒其它由于WaitFor这个Event而挂起的线程，这就是它为什么叫Event的原因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。<br />当然用一个受保护（见下面的临界区介绍）的布尔变量也能实现类似的功能，只要用一个循环检查此布尔值的代码来代替WaitFor即可。从功能上说完全没有问题，但实际使用中就会发现，这样的等待会占用大量的CPU资源，降低系统性能，影响到别的线程的执行速度，所以是不经济的，有的时候甚至可能会有问题。所以不建议这样用。<br />临界区（CriticalSection）则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操作有所不同，它只有两个操作：Enter和Leave，同样可以把它的两个状态当作True和False，分别表示现在是否处于临界区中。这两个操作也是原语，所以它可以用于在多线程应用中保护共享数据，防止访问冲突。<br />用临界区保护共享数据的方法很简单：在每次要访问共享数据之前调用Enter设置进入临界区标志，然后再操作数据，最后调用Leave离开临界区。它的保护原理是这样的：当一个线程进入临界区后，如果此时另一个线程也要访问这个数据，则它会在调用Enter时，发现已经有线程进入临界区，然后此线程就会被挂起，等待当前在临界区的线程调用Leave离开临界区，当另一个线程完成操作，调用Leave离开后，此线程就会被唤醒，并设置临界区标志，开始操作数据，这样就防止了访问冲突。<br />以前面那个InterlockedIncrement为例，我们用CriticalSection（Windows API）来实现它：<br /><br />Var<br />InterlockedCrit : TRTLCriticalSection;<br />Procedure InterlockedIncrement( var aValue : Integer );<br />Begin<br />EnterCriticalSection( InterlockedCrit );<br />Inc( aValue );<br />LeaveCriticalSection( InterlockedCrit );<br />End;<br /><br />现在再来看前面那个例子：<br />1.线程A进入临界区（假设数据为3）<br />2.线程B进入临界区，因为A已经在临界区中，所以B被挂起<br />3.线程A对数据加一（现在是4）<br />4.线程A离开临界区，唤醒线程B（现在内存中的数据是4）<br />5.线程B被唤醒，对数据加一（现在就是5了）<br />6.线程B离开临界区，现在的数据就是正确的了。<br />临界区就是这样保护共享数据的访问。<br />关于临界区的使用，有一点要注意：即数据访问时的异常情况处理。因为如果在数据操作时发生异常，将导致Leave操作没有被执行，结果将使本应被唤醒的线程未被唤醒，可能造成程序的没有响应。所以一般来说，如下面这样使用临界区才是正确的做法：<br /><br />EnterCriticalSection<br />Try<br />// 操作临界区数据<br />Finally<br />LeaveCriticalSection<br />End;<br /><br />最后要说明的是，Event和CriticalSection都是操作系统资源，使用前都需要创建，使用完后也同样需要释放。如TThread类用到的一个全局Event：SyncEvent和全局CriticalSection：TheadLock，都是在InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的，而它们则是在Classes单元的Initialization和Finalization中被调用的。<br />由于在TThread中都是用API来操作Event和CriticalSection的，所以前面都是以API为例，其实Delphi已经提供了对它们的封装，在SyncObjs单元中，分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为TEvent的构造函数参数过多，为了简单起见，Delphi还提供了一个用默认参数初始化的Event类：TSimpleEvent。<br />顺便再介绍一下另一个用于线程同步的类：TMultiReadExclusiveWriteSynchronizer，它是在SysUtils单元中定义的。据我所知，这是Delphi RTL中定义的最长的一个类名，还好它有一个短的别名：TMREWSync。至于它的用处，我想光看名字就可以知道了，我也就不多说了。<br />有了前面对Event和CriticalSection的准备知识，可以正式开始讨论Synchronize和WaitFor了。<br />我们知道，Synchronize是通过将部分代码放到主线程中执行来实现线程同步的，因为在一个进程中，只有一个主线程。先来看看Synchronize的实现：<br /><br />procedure TThread.Synchronize(Method: TThreadMethod);<br />begin<br />FSynchronize.FThread := Self;<br />FSynchronize.FSynchronizeException := nil;<br />FSynchronize.FMethod := Method;<br />Synchronize(@FSynchronize);<br />end;<br /><br />其中FSynchronize是一个记录类型：<br />PSynchronizeRecord = ^TSynchronizeRecord;<br />TSynchronizeRecord = record<br />FThread: TObject;<br />FMethod: TThreadMethod;<br />FSynchronizeException: TObject;<br />end;<br /><br />用于进行线程和主线程之间进行数据交换，包括传入线程类对象，同步方法及发生的异常。<br />在Synchronize中调用了它的一个重载版本，而且这个重载版本比较特别，它是一个“类方法”。所谓类方法，是一种特殊的类成员方法，它的调用并不需要创建类实例，而是像构造函数那样，通过类名调用。之所以会用类方法来实现它，是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一个重载版本（也是类方法）和另一个类方法StaticSynchronize。下面是这个Synchronize的代码：<br /><br />class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);<br />var<br />SyncProc: TSyncProc;<br />begin<br />if GetCurrentThreadID = MainThreadID then<br />ASyncRec.FMethod<br />else<br />begin<br />SyncProc.Signal := CreateEvent(nil, True, False, nil);<br />try<br />EnterCriticalSection(ThreadLock);<br />try<br />if SyncList = nil then<br />SyncList := TList.Create;<br />SyncProc.SyncRec := ASyncRec;<br />SyncList.Add(@SyncProc);<br />SignalSyncEvent;<br />if Assigned(WakeMainThread) then<br />WakeMainThread(SyncProc.SyncRec.FThread);<br />LeaveCriticalSection(ThreadLock);<br />try<br />WaitForSingleObject(SyncProc.Signal, INFINITE);<br />finally<br />EnterCriticalSection(ThreadLock);<br />end;<br />finally<br />LeaveCriticalSection(ThreadLock);<br />end;<br />finally<br />CloseHandle(SyncProc.Signal);<br />end;<br />if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;<br />end;<br />end;<br /><br />这段代码略多一些，不过也不算太复杂。<br />首先是判断当前线程是否是主线程，如果是，则简单地执行同步方法后返回。<br />如果不是主线程，则准备开始同步过程。<br />通过局部变量SyncProc记录线程交换数据（参数）和一个Event Handle，其记录结构如下：<br /><br />TSyncProc = record<br />SyncRec: PSynchronizeRecord;<br />Signal: THandle;<br />end;<br /><br />然后创建一个Event，接着进入临界区（通过全局变量ThreadLock进行，因为同时只能有一个线程进入Synchronize状态，所以可以用全局变量记录），然后就是把这个记录数据存入SyncList这个列表中（如果这个列表不存在的话，则创建它）。可见ThreadLock这个临界区就是为了保护对SyncList的访问，这一点在后面介绍CheckSynchronize时会再次看到。<br />再接下就是调用SignalSyncEvent，其代码在前面介绍TThread的构造函数时已经介绍过了，它的功能就是简单地将SyncEvent作一个Set的操作。关于这个SyncEvent的用途，将在后面介绍WaitFor时再详述。<br />接下来就是最主要的部分了：调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全局事件。这里之所以要用事件进行处理，是因为Synchronize方法本质上是通过消息，将需要同步的过程放到主线程中执行，如果在一些没有消息循环的应用中（如Console或DLL）是无法使用的，所以要使用这个事件进行处理。<br />而响应这个事件的是Application对象，下面两个方法分别用于设置和清空WakeMainThread事件的响应（来自Forms单元）：<br /><br />procedure TApplication.HookSynchronizeWakeup;<br />begin<br />Classes.WakeMainThread := WakeMainThread;<br />end;<br /><br />procedure TApplication.UnhookSynchronizeWakeup;<br />begin<br />Classes.WakeMainThread := nil;<br />end;<br /><br />上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。<br />这就是在Application对象中WakeMainThread事件响应的代码，消息就是在这里被发出的，它利用了一个空消息来实现：<br /><br />procedure TApplication.WakeMainThread(Sender: TObject);<br />begin<br />PostMessage(Handle, WM_NULL, 0, 0);<br />end;<br /><br />而这个消息的响应也是在Application对象中，见下面的代码（删除无关的部分）：<br />procedure TApplication.WndProc(var Message: TMessage);<br />…<br />begin<br />try<br />…<br />with Message do<br />case Msg of<br />…<br />WM_NULL:<br />CheckSynchronize;<br />…<br />except<br />HandleException(Self);<br />end;<br />end;<br />其中的CheckSynchronize也是定义在Classes单元中的，由于它比较复杂，暂时不详细说明，只要知道它是具体处理Synchronize功能的部分就好，现在继续分析Synchronize的代码。<br />在执行完WakeMainThread事件后，就退出临界区，然后调用WaitForSingleObject开始等待在进入临界区前创建的那个Event。这个Event的功能是等待这个同步方法的执行结束，关于这点，在后面分析CheckSynchronize时会再说明。<br />注意在WaitForSingleObject之后又重新进入临界区，但没有做任何事就退出了，似乎没有意义，但这是必须的！<br />因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢：<br /><br />if Assigned(WakeMainThread) then<br />WakeMainThread(SyncProc.SyncRec.FThread);<br />WaitForSingleObject(SyncProc.Signal, INFINITE);<br />finally<br />LeaveCriticalSection(ThreadLock);<br />end;<br /><br />上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响，还使代码大大简化了，但真的可以吗？<br />事实上是不行！<br />因为我们知道，在Enter临界区后，如果别的线程要再进入，则会被挂起。而WaitFor方法则会挂起当前线程，直到等待别的线程SetEvent后才会被唤醒。如果改成上面那样的代码的话，如果那个SetEvent的线程也需要进入临界区的话，死锁（Deadlock）就发生了（关于死锁的理论，请自行参考操作系统原理方面的资料）。<br />死锁是线程同步中最需要注意的方面之一！<br />最后释放开始时创建的Event，如果被同步的方法返回异常的话，还会在这里再次抛出异常。<br />回到前面CheckSynchronize，见下面的代码：<br /><br />function CheckSynchronize(Timeout: Integer = 0): Boolean;<br />var<br />SyncProc: PSyncProc;<br />LocalSyncList: TList;<br />begin<br />if GetCurrentThreadID &lt;&gt; MainThreadID then<br />raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);<br />if Timeout &gt; 0 then<br />WaitForSyncEvent(Timeout)<br />else<br />ResetSyncEvent;<br />LocalSyncList := nil;<br />EnterCriticalSection(ThreadLock);<br />try<br />Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));<br />try<br />Result := (LocalSyncList &lt;&gt; nil) and (LocalSyncList.Count &gt; 0);<br />if Result then<br />begin<br />while LocalSyncList.Count &gt; 0 do<br />begin<br />SyncProc := LocalSyncList[0];<br />LocalSyncList.Delete(0);<br />LeaveCriticalSection(ThreadLock);<br />try<br />try<br />SyncProc.SyncRec.FMethod;<br />except<br />SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;<br />end;<br />finally<br />EnterCriticalSection(ThreadLock);<br />end;<br />SetEvent(SyncProc.signal);<br />end;<br />end;<br />finally<br />LocalSyncList.Free;<br />end;<br />finally<br />LeaveCriticalSection(ThreadLock);<br />end;<br />end;<br /><br />首先，这个方法必须在主线程中被调用（如前面通过消息传递到主线程），否则就抛出异常。<br />接下来调用ResetSyncEvent（它与前面SetSyncEvent对应的，之所以不考虑WaitForSyncEvent的情况，是因为只有在Linux版下才会调用带参数的CheckSynchronize，Windows版下都是调用默认参数0的CheckSynchronize）。<br />现在可以看出SyncList的用途了：它是用于记录所有未被执行的同步方法的。因为主线程只有一个，而子线程可能有很多个，当多个子线程同时调用同步方法时，主线程可能一时无法处理，所以需要一个列表来记录它们。<br />在这里用一个局部变量LocalSyncList来交换SyncList，这里用的也是一个原语：InterlockedExchange。同样，这里也是用临界区将对SyncList的访问保护起来。<br />只要LocalSyncList不为空，则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉，退出临界区。<br />再来看对同步方法的处理：首先是从列表中移出（取出并从列表中删除）第一个同步方法调用数据。然后退出临界区（原因当然也是为了防止死锁）。<br />接着就是真正的调用同步方法了。<br />如果同步方法中出现异常，将被捕获后存入同步方法数据记录中。<br />重新进入临界区后，调用SetEvent通知调用线程，同步方法执行完成了（详见前面Synchronize中的WaitForSingleObject调用）。<br />至此，整个Synchronize的实现介绍完成。<br />最后来说一下WaitFor，它的功能就是等待线程执行结束。其代码如下：<br /><br />function TThread.WaitFor: LongWord;<br />var<br />H: array[0..1] of THandle;<br />WaitResult: Cardinal;<br />Msg: TMsg;<br />begin<br />H[0] := FHandle;<br />if GetCurrentThreadID = MainThreadID then<br />begin<br />WaitResult := 0;<br />H[1] := SyncEvent;<br />repeat<br />{ This prevents a potential deadlock if the background thread<br />does a SendMessage to the foreground thread }<br />if WaitResult = WAIT_OBJECT_0 + 2 then<br />PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);<br />WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);<br />CheckThreadError(WaitResult &lt;&gt; WAIT_FAILED);<br />if WaitResult = WAIT_OBJECT_0 + 1 then<br />CheckSynchronize;<br />until WaitResult = WAIT_OBJECT_0;<br />end else WaitForSingleObject(H[0], INFINITE);<br />CheckThreadError(GetExitCodeThread(H[0], Result));<br />end;<br /><br />如果不是在主线程中执行WaitFor的话，很简单，只要调用WaitForSingleObject等待此线程的Handle为Signaled状态即可。<br />如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent，然后循环等待，直到线程结束（即MsgWaitForMultipleObjects返回WAIT_OBJECT_0，详见MSDN中关于此API的说明）。<br />在循环等待中作如下处理：如果有消息发生，则通过PeekMessage取出此消息（但并不把它从消息循环中移除），然后调用MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态，同时监听消息（QS_SENDMESSAGE参数，详见MSDN中关于此API的说明）。可以把此API当作一个可以同时等待多个Handle的WaitForSingleObject。如果是SyncEvent被SetEvent（返回WAIT_OBJECT_0 + 1），则调用CheckSynchronize处理同步方法。<br />为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects，而不能用WaitForSingleObject等待线程结束呢？因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法，而同步方法是在主线程中执行的，如果用WaitForSingleObject等待的话，则主线程在这里被挂起，同步方法无法执行，导致线程也被挂起，于是发生死锁。<br />而改用WaitForMultipleObjects则没有这个问题。首先，它的第三个参数为False，表示只要线程Handle或SyncEvent中只要有一个Signaled即可使主线程被唤醒，至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的，所以还要防止消息被阻塞。这样，当线程中调用Synchronize时，主线程就会被唤醒并处理同步调用，在调用完成后继续进入挂起等待状态，直到线程结束。<br />至此，对线程类TThread的分析可以告一个段落了，对前面的分析作一个总结：<br />1、线程类的线程必须按正常的方式结束，即Execute执行结束，所以在其中的代码中必须在适当的地方加入足够多的对Terminated标志的判断，并及时退出。如果必须要“立即”退出，则不能使用线程类，而要改用API或RTL函数。<br />2、对可视VCL的访问要放在Synchronize中，通过消息传递到主线程中，由主线程处理。<br />3、线程共享数据的访问应该用临界区进行保护（当然用Synchronize也行）。<br />4、线程通信可以采用Event进行（当然也可以用Suspend/Resume）。<br />5、当在多线程应用中使用多种线程同步方式时，一定要小心防止出现死锁。<br />6、等待线程结束要用WaitFor方法。<!-- Added by RelatedTopic, plugin for Bo-Blog 2.0.0 --></font>
<img src ="http://www.cnitblog.com/jack29/aggbug/14682.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jack29/" target="_blank">小猪和小叮当的小爱屋</a> 2006-08-04 10:15 <a href="http://www.cnitblog.com/jack29/articles/14682.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Delphi 函数集</title><link>http://www.cnitblog.com/jack29/articles/14233.html</link><dc:creator>小猪和小叮当的小爱屋</dc:creator><author>小猪和小叮当的小爱屋</author><pubDate>Wed, 26 Jul 2006 06:50:00 GMT</pubDate><guid>http://www.cnitblog.com/jack29/articles/14233.html</guid><wfw:comment>http://www.cnitblog.com/jack29/comments/14233.html</wfw:comment><comments>http://www.cnitblog.com/jack29/articles/14233.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jack29/comments/commentRss/14233.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jack29/services/trackbacks/14233.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: abs(x)    绝对值arctan(x) 反正切cos(x)    传回馀弦函数值exp(x)    e的x次幂frac(x)   取小数部分int(x)    取整ln(x)     自然对数sin(x)    传回正弦函数值 sqr(x)    x*xsqrt(x)   平方根其它pred(x)   pred('D')='C', pred(true)=1;succ(x)   succ('Y...&nbsp;&nbsp;<a href='http://www.cnitblog.com/jack29/articles/14233.html'>阅读全文</a><img src ="http://www.cnitblog.com/jack29/aggbug/14233.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jack29/" target="_blank">小猪和小叮当的小爱屋</a> 2006-07-26 14:50 <a href="http://www.cnitblog.com/jack29/articles/14233.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>delphi中的Format函数详解(转)</title><link>http://www.cnitblog.com/jack29/articles/14230.html</link><dc:creator>小猪和小叮当的小爱屋</dc:creator><author>小猪和小叮当的小爱屋</author><pubDate>Wed, 26 Jul 2006 06:46:00 GMT</pubDate><guid>http://www.cnitblog.com/jack29/articles/14230.html</guid><wfw:comment>http://www.cnitblog.com/jack29/comments/14230.html</wfw:comment><comments>http://www.cnitblog.com/jack29/articles/14230.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/jack29/comments/commentRss/14230.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jack29/services/trackbacks/14230.html</trackback:ping><description><![CDATA[
		<p>
				<font color="#808080" size="2">Format是一个很常用，却又似乎很烦的方法，本人试图对这个方法的帮助进行一些翻译，让它有一个完整的概貌，以供大家查询之用：</font>
		</p>
		<p>
				<font color="#808080" size="2">首先看它的声明：<br />function Format(const Format: string; const Args: array of const): string; overload;</font>
		</p>
		<p>
				<font color="#808080" size="2">事实上Format方法有两个种形式，另外一种是三个参数的，主要区别在于它是线程安全的，<br />但并不多用，所以这里只对第一个介绍：<br />function Format(const Format: string; const Args: array of const): string; overload;<br /> <br />Format参数是一个格式字符串，用于格式化Args里面的值的。Args又是什么呢，<br />它是一个变体数组，即它里面可以有多个参数，而且每个参数可以不同。<br />如以下例子：</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('my name is %6s',['wind']);<br />返回后就是my name is wind</font>
		</p>
		<p>
				<font color="#808080" size="2">现在来看Format参数的详细情况：<br />Format里面可以写普通的字符串，比如'my name is',但有些格式指令字符具有特殊意义，比如"%6s"格式指令具有以下的形式：<br />"%" [index ":"] ["-"] [width] ["." prec] type<br />它是以"%"开始,而以type结束，type表示一个具体的类型。中间是用来<br />格式化type类型的指令字符，是可选的。</font>
		</p>
		<p>
				<font color="#808080" size="2">先来看看type,type可以是以下字符：<br />d 十制数，表示一个整型值<br />u 和d一样是整型值，但它是无符号的，而如果它对应的值是负的，则返回时是一个2的32次方减去这个绝对值的数,如：<br />Format('this is %u',[－2]);<br />返回的是：this is 4294967294</font>
		</p>
		<p>
				<font color="#808080" size="2">f 对应浮点数<br />e 科学表示法，对应整型数和浮点数，比如<br />Format('this is %e',[-2.22]);<br />返回的是：this is -2.22000000000000E+000,等一下再说明如果将数的精度缩小</font>
		</p>
		<p>
				<font color="#808080" size="2">g 这个只能对应浮点型，且它会将值中多余的数去掉,比如<br />Format('this is %g',[02.200]);<br />返回的是：this is 2.2</font>
		</p>
		<p>
				<font color="#808080" size="2">n 只能对应浮点型，将值转化为号码的形式。看一个例子就明白了<br />Format('this is %n',[4552.2176]);<br />返回的是this is 4,552.22</font>
		</p>
		<p>
				<font color="#808080" size="2">  注意有两点，一是只表示到小数后两位，等一下说怎么消除这种情况, 二是，即使小数没有被截断，它也不会也像整数部分一样有逗号来分开的</font>
		</p>
		<p>
				<font color="#808080" size="2">m钱币类型，但关于货币类型有更好的格式化方法，这里只是简单的格式化,另外它只对应于浮点值<br />Format('this is %m',[9552.21]);<br />返回：this is ￥9,552.21</font>
		</p>
		<p>
				<font color="#808080" size="2">p 对应于指针类型，返回的值是指针的地址，以十六进制的形式来表示<br />  例如：<br />var X:integer;<br />p:^integer;<br />begin<br />  X:=99;<br />  p:=@X;<br />  Edit1.Text:=Format('this is %p',[p]);<br />end;<br />Edit1的内容是：this is 0012F548</font>
		</p>
		<p>
				<font color="#808080" size="2">s 对应字符串类型，不用多说了吧<br />x 必须是一个整形值，以十六进制的形式返回<br />Edit1.Text:=Format('this is %X',[15]);<br />返回是：this is F</font>
		</p>
		<p>
				<font color="#808080" size="2">类型讲述完毕，下面介绍格式化Type的指令：<br />[index ":"]这个要怎么表达呢，看一个例子<br />Format('this is %d %d',[12,13]);<br />其中第一个%d的索引是0，第二个%d是1，所以字符显示的时候是这样 this is 12 13</font>
		</p>
		<p>
				<font color="#808080" size="2">而如果你这样定义：<br />Format('this is %1:d %0:d',[12,13]);<br />那么返回的字符串就变成了this is 13 12。现在明白了吗，[index ":"] 中的index指示Args中参数显示的顺序还有一种情况，如果这样<br />Format('%d %d %d %0:d %d', [1, 2, 3, 4])<br />将返回1 2 3 1 2。</font>
		</p>
		<p>
				<font color="#808080" size="2">如果你想返回的是1 2 3 1 4，必须这样定：<br />Format('%d %d %d %0:d %3:d', [1, 2, 3, 4])</font>
		</p>
		<p>
				<font color="#808080" size="2">但用的时候要注意，索引不能超出Args中的个数，不然会引起异常如<br />Format('this is %2:d %0:d',[12,13]);<br />由于Args中只有12 13 两个数，所以Index只能是0或1，这里为2就错了[width] 指定将被格式化的值占的宽度，看一个例子就明白了</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('this is %4d',[12]);<br />输出是：this is   12,这个是比较容易，不过如果Width的值小于参数的长度，则没有效果。<br />如：</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('this is %1d',[12]);<br />输出是：this is 12</font>
		</p>
		<p>
				<font color="#808080" size="2">["-"]这个指定参数向左齐，和[width]合在一起最可以看到效果：<br />Format('this is %-4d,yes',[12]);<br />输出是：this is 12   ,yes</font>
		</p>
		<p>
				<font color="#808080" size="2">["." prec] 指定精度，对于浮点数效果最佳：<br />Format('this is %.2f',['1.1234]);<br />输出 this is 1.12<br />Format('this is %.7f',['1.1234]);<br />输出了 this is 1.1234000</font>
		</p>
		<p>
				<font color="#808080" size="2">而对于整型数，如果prec比如整型的位数小，则没有效果反之比整形值的位数大，则会在整型值的前面以0补之<br />Format('this is %.7d',[1234]);<br />输出是：this is 0001234]<br />          <br />对于字符型，刚好和整型值相反，如果prec比字符串型的长度大则没有效果，反之比字符串型的长度小，则会截断尾部的字符<br />Format('this is %.2s',['1234']);<br />输出是 this is 12,而上面说的这个例子：</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('this is %e',[-2.22]);<br />返回的是：this is -2.22000000000000E+000,怎么去掉多余的0呢，这个就行啦</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('this is %.2e',[-2.22]);<br />     <br />好了，第一个总算讲完了，应该对他的应用很熟悉了吧</font>
		</p>
		<p>
				<font color="#808080" size="2">///////////////////////////////////////////////////////////////<br />二 FormatDateTime的用法<br />他的声明为：</font>
		</p>
		<p>
				<font color="#808080" size="2">function FormatDateTime(const Format: string; DateTime: TDateTime): string; <br />overload;</font>
		</p>
		<p>
				<font color="#808080" size="2">当然和Format一样还有一种，但这里只介绍常用的第一种,Format参数是一个格式化字符串。DateTime是时间类型。返回值是一种格式化后的字符串,重点来看Format参数中的指令字符</font>
		</p>
		<p>
				<font color="#808080" size="2">c 以短时间格式显示时间，即全部是数字的表示<br />FormatdateTime('c',now);<br />输出为：2004-8-7 9:55:40</font>
		</p>
		<p>
				<font color="#808080" size="2">d 对应于时间中的日期，日期是一位则显示一位，两位则显示两位<br />FormatdateTime('d',now);<br />输出可能为1～31</font>
		</p>
		<p>
				<font color="#808080" size="2">dd 和d的意义一样，但它始终是以两位来显示的<br />FormatdateTime('dd',now);<br />输出可能为01～31</font>
		</p>
		<p>
				<font color="#808080" size="2">ddd 显示的是星期几<br />FormatdateTime('ddd',now);<br />输出为: 星期六</font>
		</p>
		<p>
				<font color="#808080" size="2">dddd 和ddd显示的是一样的。 但上面两个如果在其他国家可能不一样。ddddd 以短时间格式显示年月日 <br />FormatdateTime('ddddd',now);<br />输出为：2004-8-7</font>
		</p>
		<p>
				<font color="#808080" size="2">dddddd 以长时间格式显示年月日<br />FormatdateTime('dddddd',now); <br />输出为：2004年8月7日</font>
		</p>
		<p>
				<font color="#808080" size="2">e/ee/eee/eeee 以相应的位数显示年<br />FormatdateTime('ee',now); <br />输出为：04  （表示04年）</font>
		</p>
		<p>
				<font color="#808080" size="2">m/mm/mmm/mmmm 表示月<br />FormatdateTime('m',now);<br />输出为：8<br />FormatdateTime('mm',now);<br />输出为  08<br />FormatdateTime('mmm',now);<br />输出为  八月<br />FormatdateTime('mmmm',now); <br />输出为  八月</font>
		</p>
		<p>
				<font color="#808080" size="2"> 和ddd/dddd 一样，在其他国家可能不同yy/yyyy 表示年<br />FormatdateTime('yy',now);<br />输出为 04<br />FormatdateTime('yyyy',now);<br />输出为 2004,</font>
		</p>
		<p>
				<font color="#808080" size="2">h/hh,n/nn,s/ss,z/zzz 分别表示小时，分，秒,毫秒</font>
		</p>
		<p>
				<font color="#808080" size="2">t  以短时间格式显示时间<br />FormatdateTime('t',now);<br />输出为 10:17</font>
		</p>
		<p>
				<font color="#808080" size="2">tt 以长时间格式显示时间<br />FormatdateTime('tt',now);<br />输出为10:18:46</font>
		</p>
		<p>
				<font color="#808080" size="2">ampm 以长时间格式显示上午还是下午<br />FormatdateTime('ttampm',now);<br />输出为：10:22:57上午</font>
		</p>
		<p>
				<font color="#808080" size="2">大概如此，如果要在Format中加普通的字符串，可以用双引号隔开那些特定义的字符，这样普通字符串中如果含特殊的字符就不会被显示为时间格式啦：<br />FormatdateTime('"today is" c',now);<br /> 输出为：today is 2004-8-7 10:26:58</font>
		</p>
		<p>
				<font color="#808080" size="2">时间中也可以加"-"或"\"来分开日期：<br />FormatdateTime('"today is" yy-mm-dd',now);<br />FormatdateTime('"today is" yy\mm\dd',now);<br />输出为： today is 04-08-07</font>
		</p>
		<p>
				<font color="#808080" size="2">也可以用":"来分开时间  <br />FormatdateTime('"today is" hh:nn:ss',now);<br />输出为：today is 10:32:23</font>
		</p>
		<p>
				<font color="#808080" size="2">/////////////////////////////////////////////////////////////////<br />三.FormatFloat的用法</font>
		</p>
		<p>
				<font color="#808080" size="2">常用的声明：<br />function FormatFloat(const Format: string; Value: Extended): string; overload;</font>
		</p>
		<p>
				<font color="#808080" size="2">和上面一样Format参数为格式化指令字符，Value为Extended类型为什么是这个类型，因为它是所有浮点值中表示范围最大的，如果传入该方法的参数比如Double或者其他，则可以保存不会超出范围。</font>
		</p>
		<p>
				<font color="#808080" size="2">关键是看Format参数的用法<br />0  这个指定相应的位数的指令。<br />   比如：<br />FormatFloat('000.000',22.22);<br />输出的就是022.220<br /> <br />注意一点，如果整数部分的0的个数小于Value参数中整数的位数，则没有效果如：<br />FormatFloat('0.00',22.22);<br />输出的是：22.22</font>
		</p>
		<p>
				<font color="#808080" size="2">但如果小数部分的0小于Value中小数的倍数，则会截去相应的小数和位数如：<br />FormatFloat('0.0',22.22);<br />输出的是：22.2<br />   <br />也可以在整数0中指定逗号，这个整数位数必须大于3个，才会有逗号出句<br />FormatFloat('0,000.0',2222.22);<br />输出是：2,222.2</font>
		</p>
		<p>
				<font color="#808080" size="2">如果这样<br />FormatFloat('000,0.0',2222.22);<br />它的输出还是：2,222.2</font>
		</p>
		<p>
				<font color="#808080" size="2">注意它的规律,#和0的用法一样，目前我还没有测出有什么不同。</font>
		</p>
		<p>
				<font color="#808080" size="2">FormatFloat('##.##',22.22);<br />输出是：22.00</font>
		</p>
		<p>
				<font color="#808080" size="2">E  科学表示法，看几个例子大概就明白了<br />FormatFloat('0.00E+00',2222.22);<br />输出是 2.22E+03<br />FormatFloat('0000.00E+00',2222.22);<br />输出是 2222.22E+00<br />FormatFloat('00.0E+0',2222.22);<br /> 22.2E+2<br />明白了吗，全靠E右边的0来支配的。<br />   <br />这个方法并不难，大概就是这样子了。</font>
		</p>
		<p>
				<font color="#808080" size="2">上面三个方法是很常用的，没有什么技巧，只要记得这些规范就行了。</font>
		</p>
		<p>
				<font color="#808080" size="2">总结一下Format的用法:</font>
		</p>
		<p>
				<font color="#808080" size="2">Format('x=%d',[12]);//'x=12'//最普通<br />Format('x=%3d',[12]);//'x=12'//指定宽度<br />Format('x=%f',[12.0]);//'x=12.00'//浮点数<br />Format('x=%.3f',[12.0]);//'x=12.000'//指定小数<br />Format('x=%8.2f'[12.0])//'x=12.00';<br />Format('x=%.*f',[5,12.0]);//'x=12.00000'//动态配置<br />Format('x=%.5d',[12]);//'x=00012'//前面补充0<br />Format('x=%.5x',[12]);//'x=0000C'//十六进制<br />Format('x=%1:d%0:d',[12,13]);//'x=1312'//使用索引<br />Format('x=%p',[nil]);//'x=00000000'//指针<br />Format('x=%1.1e',[12.0]);//'x=1.2E+001'//科学记数法<br />Format('x=%%',[]);//'x=%'//得到"%"<br />S:=Format('%s%d',[S,I]);//S:=S+StrToInt(I);//连接字符串<br /></font>
		</p>
<img src ="http://www.cnitblog.com/jack29/aggbug/14230.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jack29/" target="_blank">小猪和小叮当的小爱屋</a> 2006-07-26 14:46 <a href="http://www.cnitblog.com/jack29/articles/14230.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>