delphi XE-在Delphi XE 中使用go语言的并发编程方法  
官方Delphi 学习QQ群: 682628230(三千人)
频道

delphi XE-在Delphi XE 中使用go语言的并发编程方法


google的go语言最近挺热的,除了它很酷的语法外,更吸引开发者的是类似coroutine的goroutine,个人觉得它比lua的coroutine更聪明一些,因为它能在运行时自动组合线程和纤程的能力。另外go语言认为线程间的数据应该通过channel通讯,而不是用地址。这些特点非常方便开发稳定的并发式程序,也提供了清晰的并发编程思路。

go的channel携带一个数据,用于在多个coroutine之间通讯,它容易控制,因为它的规则很简单:没有值时才可以写,否则suspend,有值时才可以读,否则也suspend,并且读写是成对的操作。

其实go的思想也可以拿到delphi里用,可以用线程模仿goroutine,delphi主要用于客户端开发,所以没有go的高效率也可以接受。下面是一个实际效果的演示,希望它可以给多线程中的delphi程序员提供另一个思路。


在Delphi XE中使用go语言的defer方法例子

在Delphi XE中使用go语言的并发编程方法的例子之二

在Delphi XE中使用go语言的并发编程方法的例子之三

在Delphi XE中使用go语言的defer方法例子

{$APPTYPE CONSOLE}

uses
SysUtils,
coroutineUnit;

begin
TProc(procedure()
var
c: CChannel<Integer>; //声明一个通道,它可以携带一个整型数据。
i: Integer;
begin
c:=CChannel<Integer>.Create;

for i:=1 to 5 do //创建五个任务线程,并立即开始工作。
go( //在go语言里,有一个go关键字,用于把一个函数以goroutine方式运行,这里使用了go语言的风格,用一个go函数代替。
procedure()
begin
Sleep(1000); //假设这个任务比较复杂,花费了一秒时间。
c.value:=1; //任务完成后,给通道一个值,这个值是多少在这个示例里不重要,它仅仅是给出一个信号:我完成了
end);

for i:=1 to 5 do //对这五个任务判定是否已完成
c.value; //当value有值时,这句才能读到,否则就等待
Writeln('全部完成');
c.Free;
end)();

Readln;
end.


上一篇只提供了一个示例,可能不太容易说明什么,这里再增加一个例子。
{这个示例演示了经典的生产消费问题。
go的教程里就这个问题提供了一个Eratosthenes素数筛选法的例子,这里简化一下,演示筛选偶数吧}

我习惯把begin写成同一行,可能很多人不习惯。而这个问题在go语言里消失了,因为它要求{不能换行。

program demo2;

{$APPTYPE CONSOLE}

uses
SysUtils,
coroutineUnit;

//bug反馈:22140505@qq.com

begin
TProc(procedure()
var
i: Integer;
c: CChannel<Integer>; //声明一个通道,它可以携带一个整数,用于在两个线程之间传递
begin
c:=CChannel<Integer>.Create;
go( //启动生产线程
procedure()
var
i: Integer;
begin
for i:=1 to 10 do //制作10个整数作为产品(好弱。。),交给产生线程
c.value:=i;
c.value:=-1; //为-1时表示生产结束了(这属于这个模型的协议,我随意规定的)
end);
go( //启动消费线程,它不停的接收整数产品,判断并输出为偶数的值
procedure()
var
i: Integer;
begin
while true do begin //不停的处理,但cpu消耗不高
i:=c.value; //接收产品,如果没收到,就停在这等
if i=-1 then begin //为-1时表示生产结束了(这属于这个模型的协议,我随意规定的)
c.Free;
break;
end;
if i mod 2=0 then
writeln(inttostr(i));
end;
end);
end)();

Readln;
end.


这个例子演示了如何实现超时控制

program demo3;

{$APPTYPE CONSOLE}

uses
SysUtils,Generics.Collections,
coroutineUnit;

//这个例子演示了如何实现超时控制

var
cWork, cTimeout: CChannel<Integer>; //声明两个通道,各用于工作和超时
ret: tpair<Integer, Integer>; //后面有讲到
begin
cWork:=CChannel<Integer>.create;
cTimeout:=CChannel<Integer>.create;

go(procedure() //起动工作线程
var
i: Integer;
begin
sleep(1010); //假设这个工作费时1010毫秒
try
cWork.value:=1; //发通道发送一个值,表示工作完成了。
except
end;
end);

go(procedure() //起动一个线程用来计时
begin
sleep(1000); //假设超时等待时间是1秒
cTimeout.value:=1000; //...
end);

{直到cWork,cTimeout这两个通道至少有一个可读取,获取通道的结果,结果是个pair,其中key描述了哪个通道有响应,value描述了通道的值(对这个select的模仿远没有go语言的幽雅,也许以后会有更好的办法)}
ret:=CCoroutine.select<Integer>([cWork, cTimeout]);
case ret.key of //判断是哪个通道可读
0: Writeln('完成'); //如果是第0个,就是cWork的信号了,表示在超时等待之前工作完成了。
1: Writeln('超时'+inttostr(ret.value)); //...
end;

cWork.Free;
cTimeout.Free;
Readln;
end.


在实现函数时,如果中间的步骤出错,需要释放资源并退出函数,这些工作很繁杂,容易出错。 go语言的作者对过去十年软件开发的经历感到失望,针对这个问题,他带来了defer方法,它能让不管在函数内的哪个地方exit,都确保你有机会清扫干净。Delphi XE中也可实现一个类似的方法。 program demo_defer;

{$APPTYPE CONSOLE}

uses
SysUtils,
coroutineUnit; //还是用这个单元。。。还是在附件里

begin
TProc(procedure() //这个函数演示将一个文件的内容,拷到另一个文件里
var
f1, f2: Integer; //两个文件指针,f1的内容要拷到f2里
begin
f1:=FileOpen('f1', fmOpenRead); //打开f1文件
defer(procedure() //defer函数将参数函数保存起来,在它所属的函数退出时再调用。
begin
FileClose(f1);
Writeln('f1被关闭');
end);
f2:=FileOpen('f2', fmOpenWrite); //再打开f2文件
if f2=-1 then begin
Writeln('f2打开失败');
Exit; //果断退出,不必考虑f1的状态
end;
//copyContent(f1, f2); //开始copy(假设有这个拷贝函数存在)
FileClose(f2);
end)();

Readln;
end.

以往在处理这种情形时,需要判断f2打开是否失败,如果失败的话需要将f1关闭再退出,如果这个函数很复杂,有可能会忘记关闭,而用这个方法,确保不管你写多少个exit,关闭f1的代码都会被执行到。
实际输出的结果是 :
f2打开失败
f1被关闭


推荐分享
图文皆来源于网络,内容仅做公益性分享,版权归原作者所有,如有侵权请告知删除!
 

Copyright © 2014 DelphiW.com 开发 源码 文档 技巧 All Rights Reserved
晋ICP备14006235号-8 晋公网安备 14108102000087号

执行时间: 0.099595069885254 seconds