首页 > Delphi > 在Delphi XE 中使用go语言的并发编程方法

在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被关闭

分类:Delphi 标签:, , ,
  1. 还没有评论。
  1. No trackbacks yet.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: