多线程编程(1) - 先入门再说。
  多线程应该是编程工作者的基础技能, 但这个基础对我来讲的确有点难(起码昨天以前是这样).
  开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解.
  先试试这个:
procedure TForm1.Button1Click(Sender: TObject);
var
 i: Integer;
begin
 for i := 0 to 500000 do
 begin
  Canvas.TextOut(10, 10, IntToStr(i));
 end;
end;
  上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试...
  Delphi 为我们提供了一个简单的办法(Application.ProcessMessages)来解决这个问题:
procedure TForm1.Button1Click(Sender: TObject);
var
 i: Integer;
begin
 for i := 0 to 500000 do
 begin
  Canvas.TextOut(10, 10, IntToStr(i));
  Application.ProcessMessages;
 end;
end;

这个 Application.ProcessMessages; 一般用在比较费时的循环中, 它会检查并先处理消息队列中的其他消息.   

但这算不上多线程, 譬如: 运行中你拖动窗体, 循环会暂停下来...   在使用多线程以前, 让我们先简单修改一下程序:

function MyFun: Integer;

var  

i: Integer;

begin  

for i := 0 to 500000 do  

begin   

Form1.Canvas.Lock;   

Form1.Canvas.TextOut(10, 10, IntToStr(i));   

Form1.Canvas.Unlock;  

end;  

Result := 0;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin  

MyFun;

end;

细数上面程序的变化:

1、首先这还不是多线程的, 也会让窗体假 "死" 一会;
  2、把执行代码写在了一个函数里, 但这个函数不属于 TForm1 的方法, 所以使用 Canvas 是必须冠以名称(Form1);
  3、既然是个函数, (不管是否必要)都应该有返回值;
  4、使用了 500001 次 Lock 和 Unlock.
  Canvas.Lock 好比在说: Canvas(绘图表面)正忙着呢, 其他想用 Canvas 的等会;
  Canvas.Unlock : 用完了, 解锁!
  在 Canvas 中使用 Lock 和 Unlock 是个好习惯, 在不使用多线程的情况下这无所谓, 但保不准哪天程序会扩展为多线程的; 我们现在学习多线程, 当然应该用.
  在 Delphi 中使用多线程有两种方法: 调用 API、使用 TThread 类; 使用 API 的代码更简单.

function MyFun(p: Pointer): Integer; stdcall;
var
 i: Integer;
begin
 for i := to do
 begin
  Form1.Canvas.Lock;
  Form1.Canvas.TextOut(, , IntToStr(i));
  Form1.Canvas.Unlock;
 end;
 Result := ;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
 ID: THandle;
begin
 CreateThread(nil, , @MyFun, nil, , ID);
end;

代码分析:   

CreateThread 一个线程后, 算上原来的主线程, 这样程序就有两个线程、是标准的多线程了;   CreateThread 第三个参数是函数指针, 新线程建立后将立即执行该函数, 函数执行完毕, 系统将销毁此线程从而结束多线程的故事.   

CreateThread 要使用的函数是系统级别的, 不能是某个类(譬如: TForm1)的方法, 并且有严格的格式(参数、返回值)要求, 不管你暂时是不是需要都必须按格式来;

因为是系统级调用, 还要缀上 stdcall, stdcall 是协调参数顺序的, 虽然这里只有一个参数没有顺序可言, 但这是使用系统函数的惯例.   

CreateThread 还需要一个 var 参数来接受新建线程的 ID, 尽管暂时没用, 但这也是格式; 其他参数以后再说吧.

这样一个最简单的多线程程序就出来了, 咱们再用 TThread 类实现一次

type
 TMyThread = class(TThread)
 protected
  procedure Execute; override;
 end;
procedure TMyThread.Execute;
var
 i: Integer;
begin
 FreeOnTerminate := True; {这可以让线程执行完毕后随即释放}
 for i := to do
 begin
  Form1.Canvas.Lock;
  Form1.Canvas.TextOut(, , IntToStr(i));
  Form1.Canvas.Unlock;
 end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
 TMyThread.Create(False);
end;

TThread 类有一个抽象方法(Execute), 因而是个抽象类, 抽象类只能继承使用, 上面是继承为 TMyThread.
  继承 TThread 主要就是实现抽象方法 Execute(把我们的代码写在里面), 等我们的 TMyThread 实例化后, 首先就会执行 Execute 方法中的代码.
  按常规我们一般这样去实例化:

procedure TForm1.Button1Click(Sender: TObject);
var
 MyThread: TMyThread;
begin
 MyThread := TMyThread.Create(False);
end;

因为 MyThread 变量在这里毫无用处(并且编译器还有提示), 所以不如直接写做 TMyThread.Create(False);
  我们还可以轻松解决一个问题, 如果: TMyThread.Create(True) ?

procedure TForm1.Button1Click(Sender: TObject);
var
 MyThread: TMyThread;
begin
 MyThread := TMyThread.Create(True);
 MyThread.Resume;
end;
//可简化为:
procedure TForm1.Button1Click(Sender: TObject);
begin
 with TMyThread.Create(True) do Resume;
end;

使用 TThread 类时, Delphi 有提供的模板, 但用 IDE 写代码很方便, 我重写一遍录下来给大家看:
  多线程编程(1) - 先入门再说
  期间使用了 Ctrl+J、Shift+Ctrl+C、Ctrl+Alt+P 等快捷键.
  重要的修正与补充: 在 TThread 类的例子中, 应该有这句: FreeOnTerminate := True; (原来漏掉, 代码中已加上; 但动画加不上了).
  先说它什么意思:
  类 Create 了就要 Free;
  但 TThread(的子类) 有特殊性, 很多时候我们不能确定新建的线程什么时候执行完(也就是什么时候该释放);
  如果线程执行完毕自己知道释放就好了, 所以 TThread 给了一个布尔属性 FreeOnTerminate, 如果为 True, 线程执行完毕后就会自释放.
  我怎么会忽略了这么重要的问题呢? 原因有二:
  1、我一直在追求最精炼的代码;
  2、我手头上不只一本书上介绍说: FreeOnTerminate 的默认值是 True(错误!), 经落实, 应该是 False, 起码在 Delphi 2007 和 2009 中是这样; 或许以前的某个版本和现在不一样

多线程编程(2) - 从 CreateThread 说起。

function CreateThread(
 lpThreadAttributes: Pointer;      {安全设置}
 dwStackSize: DWORD;          {堆栈大小}
 lpStartAddress: TFNThreadStartRoutine; {入口函数}
 lpParameter: Pointer;         {函数参数}
 dwCreationFlags: DWORD;        {启动选项}
 var lpThreadId: DWORD         {输出线程 ID }
): THandle; stdcall;           {返回线程句柄}
  在 Windows 上建立一个线程, 离不开 CreateThread 函数;

在 Windows 上建立一个线程, 离不开 CreateThread 函数;
  TThread.Create 就是先调用了 BeginThread (Delphi 自定义的), BeginThread 又调用的 CreateThread.
  既然有建立, 就该有释放, CreateThread 对应的释放函数是: ExitThread, 譬如下面代码:

procedure TForm1.Button1Click(Sender: TObject);
begin
 ExitThread(); {此句即可退出当前程序, 但不建议这样使用}
end;

代码注释:   当前程序是一个进程, 进程只是一个工作环境, 线程是工作者;   每个进程都会有一个启动线程(或叫主线程), 也就是说: 我们之前大量的编码都是写给这个主线程的;   上面的 ExitThread(0); 就是退出这个主线程;   系统不允许一个没有线程的进程存在, 所以程序就退出了.   另外: ExitThread 函数的参数是一个退出码, 这个退出码是给之后的其他函数用的, 这里随便给个无符号整数即可.   或许你会说: 这个 ExitThread 挺好用的; 其实不管是用 API 还是用 TThread 类写多线程, 我们很少用到它; 因为:   1、假如直接使用 API 的 CreateThread, 它执行完入口函数后会自动退出, 无需 ExitThread;

2、用 TThread 类建立的线程又绝不能使用 ExitThread 退出; 因为使用 TThread 建立线程时会同时分配更多资源(譬如你自定义的成员、还有它的祖先类(TObject)分配的资源等等), 如果用 ExitThread 给草草退出了, 这些资源将得不到释放而导致内存泄露. 尽管 Delphi 提供了 EndThread(其内部调用 ExitThread), 这也不需要我们手动操作(假如非要手动操作也是件很麻烦的事情, 因为很多时候你不知道线程是什么时候执行完毕的).   除了 CreateThread, 还有一个 CreateRemoteThread, 可在其他进程中建立线程, 这不应该是现在学习的重点;   现在先集中精力把 CreateThread 的参数搞彻底.   倒着来吧, 先谈谈 CreateThread 将要返回的 "线程句柄".   "句柄" 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;   有句柄的对象一般都是系统级别的对象(或叫内核对象); 之所以给我们的是句柄而不是指针, 目的只有一个: "安全";   貌似通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后, 我们也就到此为止很难了解更多了; 事实上是系统并不相信我们.   不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.   

既然 CreateThread 可以返回一个句柄, 说明线程属于 "内核对象".   实际上不管线程属于哪个进程, 它们在系统的怀抱中是平等的; 在优先级(后面详谈)相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行.   这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?

有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的**, 线程是数据就是通过这个结构切换的, 我们也可以通过 GetThreadContext 函数读取寄存器看看.

附上这个结构 TContext(或叫: CONTEXT、_CONTEXT) 的定义Context = ^TContext;
_CONTEXT = record
 ContextFlags: DWORD;
 Dr0: DWORD;
 Dr1: DWORD;
 Dr2: DWORD;
 Dr3: DWORD;
 Dr6: DWORD;
 Dr7: DWORD;
 FloatSave: TFloatingSaveArea;
 SegGs: DWORD;
 SegFs: DWORD;
 SegEs: DWORD;
 SegDs: DWORD;
 Edi: DWORD;
 Esi: DWORD;
 Ebx: DWORD;
 Edx: DWORD;
 Ecx: DWORD;
 Eax: DWORD;
 Ebp: DWORD;
 Eip: DWORD;
 SegCs: DWORD;
 EFlags: DWORD;
 Esp: DWORD;
 SegSs: DWORD;
end;

CreateThread 的最后一个参数是 "线程的 ID";
  既然可以返回句柄, 为什么还要输出这个 ID? 现在我知道的是:
  1、线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等.
  2、ID 比句柄更轻便.
  在主线程中 GetCurrentThreadId、MainThreadID、MainInstance 获取的都是主线程的 ID.
  啰哩啰嗦一大些, 才说了 CreateThread 一个参数, 下篇继续.
  线程的学习还在入门中, 把我的理解写在这, 最期望的收获是得到指正.
多线程编程(3) - 从 CreateThread 说起[续]。

function CreateThread(
 lpThreadAttributes: Pointer;
 dwStackSize: DWORD;
 lpStartAddress: TFNThreadStartRoutine;
 lpParameter: Pointer;
 dwCreationFlags: DWORD; {启动选项}
 var lpThreadId: DWORD
): THandle; stdcall;
  CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:
  0: 线程建立后立即执行入口函数;
  CREATE_SUSPENDED: 线程建立后会挂起等待.
  可用 ResumeThread 函数是恢复线程的运行; 可用 SuspendThread 再次挂起线程.
  这两个函数的参数都是线程句柄, 返回值是执行前的挂起计数.
  什么是挂起计数?
  SuspendThread 会给这个数 +1; ResumeThread 会给这个数 -1; 但这个数最小是 0.
  当这个数 = 0 时, 线程会运行; > 0 时会挂起.
  如果被 SuspendThread 多次, 同样需要 ResumeThread 多次才能恢复线程的运行.
  在下面的例子中, 有新线程不断给一个全局变量赋随机值;
  同时窗体上的 Timer 控件每隔 1/10 秒就把这个变量写在窗体标题;
  在这个过程中演示了 ResumeThread、SuspendThread 两个函数.
  运行效果图:
  
  代码文件:unit Unit1;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, ExtCtrls;
type
 TForm1 = class(TForm)
  Button1: TButton;
  Button2: TButton;
  Button3: TButton;
  Timer1: TTimer;
  procedure Button1Click(Sender: TObject);
  procedure Button2Click(Sender: TObject);
  procedure Button3Click(Sender: TObject);
  procedure FormCreate(Sender: TObject);
  procedure Timer1Timer(Sender: TObject);
 end;
var
 Form1: TForm1;
implementation
{$R *.dfm}
var
 hThread: THandle; {线程句柄}
 num: Integer;   {全局变量, 用于记录随机数}
{线程入口函数}
function MyThreadFun(p: Pointer): Integer; stdcall;
begin
 while True do {假如线程不挂起, 这个循环将一直循环下去}
 begin
  num := Random(100);
 end;
 Result := 0;
end;
{建立并挂起线程}
procedure TForm1.Button1Click(Sender: TObject);
var
 ID: DWORD;
begin
 hThread := CreateThread(nil, 0, @MyThreadFun, nil, CREATE_SUSPENDED, ID);
 Button1.Enabled := False;
end;
{唤醒并继续线程}
procedure TForm1.Button2Click(Sender: TObject);
begin
 ResumeThread(hThread);
end;
{挂起线程}
procedure TForm1.Button3Click(Sender: TObject);
begin
 SuspendThread(hThread);
end;
{下面是窗体代码}
procedure TForm1.FormCreate(Sender: TObject);
begin
 Timer1.Interval := 100;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 Text := IntToStr(num);
end;
end.

delphi2010多线程编程教程的更多相关文章

  1. c++11 多线程入门教程(一)

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/10945309.html 最近在找c++服务端开发的实习(大佬们有推荐吗QAQ..),恰好写了一 ...

  2. C++多线程编程(教程+Demo)

    下载地址:C++多线程编程(教程+Demo) Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步.互斥和临界区等操作.Visual C++ 6.0中,使用MFC类库也实现 ...

  3. Java-Runoob-高级教程: Java 多线程编程

    ylbtech-Java-Runoob-高级教程: Java 多线程编程 1.返回顶部 1. Java 多线程编程 Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流, ...

  4. Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介

    Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...

  5. ASP.Net教程系列:多线程编程实战(一)

    Web开发中使用多线程可以增强用户体验,尤其是多用户.多任务.海量数据和资源紧张的情况下.所以我们的ASP.Net教程设立多线程编程实战专题.下面这些代码范例都是入门级的,希望对对大家学习ASP.Ne ...

  6. Java之旅_高级教程_多线程编程

    摘自:http://www.runoob.com/java/java-multithreading.html Java 多线程编程 Java 给多线程编程提供了内置的支持.一条线程指的是进程中的一条执 ...

  7. 深入HTML5 Web Worker应用实践:多线程编程

    HTML5 中工作线程(Web Worker)简介 至 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越多崭新的特性和功能.它不但强化了 Web 系统或网页的表现性能 ...

  8. 《转》VC++多线程编程

    原地址:http://www.cnblogs.com/wxfasdic/archive/2010/09/23/1833522.html 留个纪念,不错的总结.十个例子清晰列举啦多线程编程的奥妙.  V ...

  9. 深入 HTML5 Web Worker 应用实践:多线程编程

    深入 HTML5 Web Worker 应用实践:多线程编程 HTML5 中工作线程(Web Worker)简介 至 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越 ...

随机推荐

  1. setTimeout调用带参数的函数的方法

    function test(s){    alert(s);}window.setTimeout(function(){test('str');},1000);这样就可以了...为什么是这样呢.因为s ...

  2. Make cnblogs mobile Compatible

    Introduction 博客园的许多页面都是只支持PC的,没有手机端的,也没有手机端的app.每次查看都有放大了才能看清楚字体,手指和游泳似的,左右开弓,很不方便.还有上次我修改了theme之后,手 ...

  3. 与TCP/IP协议的初次见面(一)

    引言 最近LZ有了一点时间,于是便拿出TCP/IP的书本开始啃.开始的时候,啃起来枯燥无味,现在好不容易有点开窍,于是赶忙记录一下,生怕自己一转眼就给忘了.不过计算机系统原理就有点可惜了,最近一直没时 ...

  4. encodeURI(encodeURI(name)) ;文件上传

    window.location.href = xxxx?a=encodeURI(encodeURI(name)) ;// 编码name是中文,页面部分需要编码两次name = java.net.URL ...

  5. java三大特性性:封装、继承和多态

    一.封装 封装是指隐藏对象的属性及实现细节,对外仅提供接口可见.封装实现了信息隐藏,利于软件复用.其优点是达到了模块化的标准,从而提高了代码的复用程度.在某种程度上,封装也大大改善了软件开发的可维护性 ...

  6. C#基础语法总结

    C#笔记——基础篇 一.入门知识 VS中的常用快捷键 Ctrl+K+D:快速对齐代码 Ctrl+Z:撤销 Ctrl+S:保存(一定要经常保存!) Ctrl+J:快速弹出智能提示 Shift+End . ...

  7. Sftp和ftp 差别、工作原理等(汇总ing)

    Sftp和ftp over ssh2的差别 近期使用SecureFx,涉及了两个不同的安全文件传输协议: -sftp -ftp over SSH2 这两种协议是不同的.sftp是ssh内含的协议,仅仅 ...

  8. WCF入门教程系列三

    一.WCF服务应用程序与WCF服务库 我们在平时开发的过程中常用的项目类型有“WCF 服务应用程序”和“WCF服务库”. WCF服务应用程序,是一个可以执行的程序,它有独立的进程,WCF服务类契约的定 ...

  9. C#中static void Main(string[] args) 参数详解

    学习C#编程最常见的示例程序是在控制台应用程序中输出Hello World! using System; namespace DemoMainArgs { class Program { static ...

  10. 搭建和测试 Redis 主备和集群

    本文章只是自我学习用,不适宜转载. 1. Redis主备集群 1.1 搭建步骤 机器:海航云虚机(2核4GB内存),使用 Centos 7.2 64bit 操作系统,IP 分别是 192.168.10 ...