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