Perl线程是一个单一的执行流程,它是所有程序执行过程中最小的控制单位,即能被CPU所调度的最小任务单元。Perl线程与进程之间既有联系,又完全不同。简单地说,一个Perl线程必然属于某一个进程,而一个进程包含至少一个或者多个Perl线程。早期的计算机系统一次只能运行一个程序,因此当有多个程序需要执行的时候,唯一的办法就是让它们排成队,按顺序串行执行。进程的出现打破了这种格局,CPU资源按时间片被分割开来,分配给不同的进程使用。

这样一来,从微观上看进程的执行虽然仍是串行的,但是从宏观上看,不同的程序已经是在并行执行了。如果我们把同样的思想运用到进程上,很自然地就会把进程再细分成更小的执行单位,即Perl线程。由于一个进程又往往需要同时执行多个类似的任务,因此这些被细分的Perl线程之间可以共享相同的代码段,数据段和文件句柄等资源。有了进程,我们可以在一台单CPU计算机系统上同时运行Firefox和MicrosoftOfficeWord等多个程序;有了Perl线程,我们可以使Firefox在不同的标签里同时加载多个不同的页面,在OfficeWord里编辑文档的同时进行语法错误检查。因此,Perl线程给我们带来了更高的CPU利用率、更快速的程序响应、更经济地资源使用方式和对多CPU的体系结构更良好的适应性。

Perl最多允许64个子线程,加上主线程因此最多65个线程。

Perl线程的生命周期

创建Perl线程

Perl线程作为Perl中的一种实体,其一生可以粗略的分为创建,运行与退出这三个阶段。创建使得Perl线程从无到有,运行则是Perl线程完成其主要工作的阶段,退出自然就是指Perl线程的消亡。Perl线程的运行和普通函数的执行非常类似,有其入口参数,一段特定的代码流程以及执行完毕后返回的一个或一组结果,唯一与普通函数调用的不同之处就在于新建Perl线程的执行与当前Perl线程的执行是并行的。

Perl里创建一个新的Perl线程非常简单,主要有两种方法,他们分别是:

 使用threads包的create()方法

使用async{}块创建Perl线程

信号量

Thread::Semaphore包为Perl线程提供了信号量的支持。你可以创建一个自己的信号量,并通过down操作和up操作来实现对资源的同步访问。实际上,down操作和up操作对应的就是我们所熟知的P操作和V操作。从内部实现上看,Thread::Semaphore本质上就是加了锁的共享变量,无非是把这个加了锁的共享变量封装成了一个Perl线程安全的包而已。由于信号量不必与任何变量绑定,因此,它非常灵活,可以用来控制你想同步的任何数据结构和程序行为。例如

use threads;
use threads::shared;
use Thread::Semaphore;
my $s=Thread::Semaphore->new();
$s->down();#Poperation
... 
$s->up();#Voperation

从本质上说,信号量是一个共享的整型变量的引用。默认情况下,它的初始值为1,down操作使它的值减1,up操作使它的值加1。当然你也可以自定义信号量初始值和每次up或down操作时信号量的变化。例如

Perl线程中的信号量

use threads;
use Thread::Semaphore;
 
my $s=Thread::Semaphore->new(5);
printf("s=".${$s}."\n");#s=5 
$s->down(3);
printf("s=".${$s}."\n");#s=2 
...  
$s->up(4);
printf("s=".${$s}."\n");#s=6

Perl线程队列

Thread::Queue包为Perl线程提供了Perl线程安全的队列支持。与信号量类似,从内部实现上看,Thread::Queue也是把一个通过锁机制实现同步访问的共享队列封装成了一个Perl线程安全的包,并提供统一的使用接口。Thread::Queue在某些情况下可以大大简化Perl线程间通信的难度和成本。例如在生产者-消费者模型中,生产者可以不断地在Perl线程队列上做enqueue操作,而消费者只需要不断地在Perl线程队列上做dequeue操作,这就很简单地实现了生产者和消费者之间同步的问题。例如

生产者-消费者模型中对Perl线程队列的使用
use threads;
use Thread::Queue;
my $q=Thread::Queue->new();

sub produce{
my $name=shift;
while(1){
 my $r=int(rand(100));
 $q->enqueue($r);
 printf("$name produce $r\n");
 sleep(int(rand(3)));
}
}
sub consume{  
 my$name=shift;
 while(my$r=$q->dequeue()){  
  printf("consume$r\n");
 }  
}  
 
my $producer1=threads->create(\&produce,"producer1");
my $producer2=threads->create(\&produce,"producer2");
my $consumer1=threads->create(\&consume,"consumer2");

$producer1->join();
$producer2->join();
$consumer1->join();

其他有用的非核心包

本文前面讨论的所有内容都在Perl线程核心包的范畴之内。其实CPAN上还有其他一些与Perl线程相关的非核心包,它们往往也会给Perl线程的使用带来很大的便利,这里我们选出两个稍加介绍,抛砖引玉。

Thread::Pool包允许你在程序中创建一批Perl线程去完成多个类似的任务。例如当你希望创建一个多Perl线程程序去完成检验1000个ip地址是否都能ping通的任务时,Thread::Pool包可以给你带来便利。

Thread::RWLock包为Perl线程中的读写操作提供了锁机制的支持。例如当你有多个reader和writerPerl线程共同访问某一个或几个文件时,Thread::RWLock包可以给你带来便利。

Perl线程的消亡

大多数情况下,你希望你创建的Perl线程正常退出,这就意味着Perl线程所对应的函数体在执行完毕后返回并释放资源。例如在清单5的示例中,新建Perl线程被join以后的退出过程。可是,如果由于detach不当或者由于主线因某些意外的异常提前结束了,尽管它所创建的Perl线程可能尚未执行完毕,但是他们还是会被强制中止,正所谓皮之不存,毛将焉附。这时你也许会得到一个类似于“Perl exited with active threads”的警告。

当然,你也可以显示地调用exit()方法来结束一个Perl线程,不过值得注意的是,默认情况下,如果你在一个Perl线程中调用了exit()方法,其他Perl线程都会随之一起结束,在很多情况下,这也许不是你想要的,如果你希望exit()方法只在调用它的Perl线程内生效,那么你在创建该Perl线程的时候就需要设置’exit’=>’thread_only’。例如为某个Perl线程设置’exit’=>’thread_only’属性。
use threads;
sub say_hello{
printf("Hello thread!@_.\n");
sleep(10);
printf("Bye\n");
}

sub quick_exit{
printf("I will be exit in no time\n");
exit(1);
}

my$t1=threads->create(\&say_hello,"param1","param2");
my$t2=threads->create({'exit'=>'thread_only'},\&quick_exit);
 
$t1->join();
$t2->join();

如果你希望每个Perl线程的exit方法都只对自己有效,那么在每次创建一个新Perl线程的时候都去要显式设置’exit’=>’thread_only’属性显然有些麻烦,你也可以在引入threads包的时候设置这个属性在全局范围内有效,可以设置’exit’=>’thread_only’为全局属性。

use threads('exit'=>'threads_only');
subfunc{  
...
if($condition){  
exit(1);
}} 
 
my$t1=threads->create(\&func);
my$t2=threads->create(\&func);
 
$t1->join();
$t2->join();

如果想共享一个变量,需通过threads::shared来实现。在使用此方法的时候,需要注意以下三点:

变量默认是不在线程中共享的。
 通过"usethreads"引用命名空间,不能通过eval,do,或者require。
 如果有变量需要共享,必须引用"threads::shared"。并在定义变量的时候如下:
my$var1:shared="value";

---------------------------------------------------
取得线程的返回值

my $rda=$thread->join();#retrun data

其所派生的线程函数中有其返回值。

其中有几个需要注意的地方:

thread的默认返回值是scalar

如果sub的返回值是array,可以返回array的ref如上例,或者直接在 create 时传入hash ref指定:$thread->create({'context'=>'list'},\&grabdata,$cnip,$snip) 。

join VS detach

上例用到join只是因为我需要每个 grabdata sub 的返回值,如果创建Thread只是为了达到并行处理的目的,线程的返回值不重要,可以创建后 detach 。

危险的IO Buffer

这个虽然和多线程没有直接关系,但是令我印象深刻,所以还是记录一下。因为以前用到 file handler 从来不会手动 close,在这里就悲剧了。Perl在处理 file handler 的时候为了减少文件IO以提高性能,有一个 buffer 机制。比如上例在for循环中写入$OUT,perl并不会在每次循环都写入文件,而是暂时保存在 buffer 里。如果不手动 close 的话,最终上传的将是一个空文件。Perl 诡异的 Buffer 迷惑了很多人。

内置的thread模块可以满足一般多线程的需求,但是没有解决一些我们经常会碰到的问题:

无法固定线程个数,排队处理

比如,我需要发送1000个http request,显然同时发送害人害己。比较自然的想法是,创建比如20个线程,某一个线程完毕后,程序自动补上一个,保证同时只有20个线程。事实上,thread 结合 thread::queue 模块可以实现,但是十分费解,手动要做的事情太多,而且容易侧漏。

线程之间变量共享比较困难

上例中,各个线程的变量是独立的。但是很多情况下我们需要所有线程共享一个数据结构,比如 push 数据到同一个array,thread 模块无法解决。thread::shared 模块号称能够共享变量,但是对于array和hash还是无能为力。

---------------------------------------------------
Perl线程的历史

5005threadsPerl线程模型

Perl对Perl线程的支持最早可以追溯到1998年7月发布的Perlv5.005。其发布申明指出,Perlv5.005中加入了对操作系统级Perl线程的支持,这个新特性是一个实验性的产品,这也就是我们现在所称的5005threadsPerl线程模型。对于5005threadsPerl线程模型来说,默认情况下,所有数据结构都是共享的,所以用户必须负责这些共享数据结构的同步访问。如今5005threads已经不再被推荐实用,Perlv5.10以后的版本里,也将不会再支持5005threadsPerl线程模型。

ithreadsPerl线程模型

2000年5月发布的Perlv5.6.0中开始引入了一个全新的Perl线程模型,即interpreterthreads,或称为ithreads,也正是在这个版本的发布申明中第一次提出了5005threadsPerl线程模型将来可能会被禁用的问题。尽管如此,ithreads在那个时候还是一个新的实验性的Perl线程模型,用户并不能直接使用它,唯一的办法是通过fork函数模拟。经过两年时间的发展,到2002年7月,Perlv5.8.0正式发布,这时ithreads已经是一个相对成熟的Perl线程模型,发布申明中也鼓励用户从老的5005threadsPerl线程模型转换到新的ithreadsPerl线程模型,并明确指出5005threadsPerl线程模型最终将被淘汰。本文后面所讨论的所有内容也都是基于新的ithreadsPerl线程模型。在ithreadsPerl线程模型中,最与众不同的特点就在于默认情况一下一切数据结构都不是共享的,这一点我们会在后面内容中有更深刻的体会。

现有环境支持哪种Perl线程模型

既然Perl中有可能存在两种不同的Perl线程模型,我们很自然地就需要判断现有Perl环境到底支持的是哪一种Perl线程实现方式。归纳起来,我们有两种方法:

shell中查询Perl当前Perl线程模型
perl-V|grepuse.*threads

从结果中不难看出,在当前的Perl环境中提供了对ithreadsPerl线程模型的支持。在Perl程序中,我们也可以通过使用Config模块来动态获取Perl线程模型的相关信息,例如

Perl程序中动态获取当前Perl线程模型

值得一提的是,对于5005threads和ithreadsPerl线程模型,Perl同时只能支持其中的一种。你不可能在某一个Perl环境中同时使用这两种Perl线程模型,本文后面讨论的所有内容都是基于ithreadsPerl线程模型的。

---------------------------------------------------
Perl 的线程中的锁

在Linux中线程和进程和最大分别,可能就是有共享变量就个东西了.其它的地方使用起来感觉不大,只是线程利用cpu更加高效,但是也更加容易出问题.下面就看看Perl中的锁和相关的问题.线程是一个好东西,它不象进程占用那么多的内存,因不它不需要主空间,不需要进程控制块.它只共享所有主进程的所有内容.

use threads;
use threads::shared;
use Data::Dumper;

my $val:shared; # 共享变量
my %hash:shared; # 共享数组
my @array:shared; # 共享哈希

my $t1 = threads->create(\&test1);
my $t2 = threads->create(\&test2);

$t1->join; # 回收 t1 的线程
$t2->join;

print Dumper(\$val);
print Dumper(\@array);
print Dumper(\%hash);

sub test1 {
 lock ($val); lock (@array); lock (%hash);
 for ( 1 .. 1000 ){
  $val++;
  $array[0]++;
  $hash{test}++;
 }  
}

sub test2 {
 lock ($val); lock (@array); lock (%hash);
 for ( 1 .. 1000 ){
  $val++;
  $array[0]++;
  $hash{test}++;
 }  
}

我们可以看出,在使用共享变量是,我们一定要使用一个 lock 来锁定当前需要共享的变量,这需要一个排它性.不然很容易出问题,不锁的话,会出什么问题啦,我们来看下面的例子,出掉锁以后会怎么样(即注释掉有'lock...'的那一行).在 Perl 中有个子函数的 lock ,也有对象的方法锁,使用的方法是:sub test1 : locked {...},但这种方法已经被弃用,不再受支持。

引用的线程共享

上面是使用的普通的变量的共享变量,但我们能使用引用和对象来共享吗?这是我们比较关心的.我们下面就来测试一下,引用在共享变量中是否能用.
use threads;
use threads::shared;
my %hash:shared;
 
my $t = threads->create(\&test);
$t->join;
 
sub test{$hash{test}{test}++;}

输出我们发现如下:
Thread 1 terminated abnormally: Invalid value for shared scalar at threads2.pl line 10.

我们发现,我们建了一个共享的变量,%hash 时,没有问题.但我们向$hash{test}中放一个匿名的哈希变量时,被 threads::shared 模块检查出来,讲这个不能放入,所以导致错误.因为匿名的哈希没有声明是不能共享的.
use threads;
use threads::shared;
my %hash:shared;
$hash{test}=&share({});
my $t = threads->create(\&test);
$t->join;
sub test{$hash{test}{test}++;}

在这多了一个 $hash{test}=&share({});

我们告诉线程,我们将要放入一个共享的匿名的哈希变量来做为引用.这时就可以使用引来了.所以在这种情况我们需要先声明匿名的哈希变量来做为引用.这时如果有多级的数据结构的引用也可以,使用相同的方法就行了,象 test 下面如果还有一个 try 的话,就使用 $hash{test}{try} = &share ({});来一级一级的声明.如果要使用变量的引用,这时我们可以使用 threads::shared 提供给我们的 shared_clone 功能。
use v5.12;
use threads;
use threads::shared;
use Data::Dumper;

my $obj:shared;
$obj=shared_clone({'foo' => [qw/foo bar baz/],'a'=>1});
say Dumper $obj;

my $th1 = threads->create(\&test1);
my $th2 = threads->create(\&test2);
$th1->join;
$th2->join;

sub test1{
 lock($obj);
 $obj->{'b'}=2;
 pop @{$obj->{'foo'}};
 say Dumper $obj;
}

sub test2{
 lock($obj);
 delete $obj->{'a'};
 push @{$obj->{'foo'}},'me';
 delete $obj->{'b'};
 say threads->tid().Dumper($obj);
}

对象的线程共享

我们常常想在线程中使用同一个对象,多个线程来操作同一个对象,这时线程中是怎么做啦?默认的对象是在线程中不共享的,也不能正常的使用的。下面的方法可以使用对象,在线程中,但要注意,我们使用时也需要对对象进行lock;不然会和上面所讲到的,线程中的锁中没锁一样,数据会乱掉。
use threads;
use threads::shared;

my $obj = &shared_clone (FreeOA->new); # 要给变量声明成共享
my $th1 = threads->create(\&test1);
my $th2 = threads->create(\&test2);
$th1->join;
$th2->join;
$obj->display;
 
sub test1 {
 lock ($obj);
 for( 1 .. 1000 ){
  $obj->add_number();
 }  
}

sub test2 {
 lock ($obj);
 for( 1 .. 1000 ){
  $obj->add_number();
 }  
}

package FreeOA;
sub new {
 my $class = shift;
 my $data = { 'number' => 0 };
 my $self = bless ($data, $class);
 return $self;
}

sub add_number {
 my $self = shift;
 $self->{number} ++;
}
 
sub display {
 my $self = shift;
 print $self->{number}."\n"
}

1;

这其实一样是使用的 shared_clone 记的使用对象的时候,需要锁。

参考来源:http://www.php-oa.com

Perl线程开发过程中的经验相关推荐

  1. Android开发过程中的部分经验总结

    该文章为Android App 开发过程中遇到的常见问题总结,该总结也会持续不断的优化 完善当中.后续开发中一定会遇到各种各样的问题, 这些问题会酌情不断补充进来. 我将遇到的问题分为两大类,非技术问 ...

  2. TimeWalker游戏开发过程中的问题,经验与反思

    博客里没东西看着慌先复制粘贴点东西上去...这一篇是我在某个课程结束写的课程论文,讲的是在一个叫timewalker的unity2d游戏开发过程中以及之后总结的经验教训.写成了论文的格式,下面只放出了 ...

  3. web开发过程中经常用到的一些公共方法及操作

    进化成为程序猿也有段岁月了,所谓的经验,广度还是依旧,只不过是对于某种功能有了多种实现方式的想法.每天依旧不厌其烦的敲打着代码,每一行代码的回车似乎都有一种似曾相识的感觉.于是乎:粘贴复制,再粘贴再复 ...

  4. android开发过程中遇到的问题

    记录android开发过程中遇到的问题. 1.在一个xml中能否使用同一个include多次 http://www.apkbus.com/android-104152-1-1.html android ...

  5. net开发过程中,错误集锦

    2007年 6月 移动项目: 1:vs2005上安装vss2005后,不能够显示源代码管理 (http://www.cnblogs.com/SGSoft/archive/2007/06/12/7803 ...

  6. .net开发过程中,错误集锦

    2007年7月10日 开发过程中,错误集锦. 写这个东东的目的,主要是上班那不能够上网,回家后总结出来弄到日志本里面的.顺便鄙视下移动,对厂家这么刻薄,不能上网,不能带水进去喝,最气人的是,进出大楼, ...

  7. 项目开发过程中的收获与思考

    2013年7月,我正式毕业了,到公司入职,也就正式成为了一名菜鸟程序员.到今天,2014年1月3日,目前主要的工作是公司一个项目中的一个功能模块,到我进入项目组算起,已经过了四个月了.因此,想写点东西 ...

  8. 人工智能和大数据的开发过程中需要注意这12点

    https://www.toutiao.com/i6636522371094151694/ 2018-12-19 10:16:15 人工智能是近年来科技发展的重要方向,在大数据时代,对数据采集.挖掘. ...

  9. 移动应用开发过程中的迭代式原型设计

    \ 主要结论 \ 移动应用原型创建过程中采用迭代式快速开发方法的重要性. \ 可以从对手身上学到什么,如何从他们的失误中获益. \ 如何为你的应用定义USP,如何通过故事板(Storyboarding ...

最新文章

  1. Linux大神必会操作——系统排错
  2. Python心得--如何提高代码质量
  3. python下载网页里面所有的图片-Python批量下载网页图片详细教程
  4. Python 图像处理篇-利用opencv库展示本地图片实例演示
  5. C-Lodop回调函数的触发
  6. php websocket 是否在线_看完让你彻底理解WebSocket原理,附实战代码(包含前端和后端)...
  7. 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件
  8. 软件系统性能优化策略--SQL优化
  9. postgresql 集合类型_PostgreSQL数据类型-时间数据类型
  10. hdu 4323 Magic Number dp 多校联合赛(三)第四题
  11. Drools7中文教程 文档 指南
  12. element ui html编辑器,vue+element-ui 使用富文本编辑器
  13. 计195班学子博文目录
  14. 云计算技术基础【9】
  15. 按照姓氏笔画排序数据、按照姓氏拼音排序数据
  16. 删除google网页快照方法
  17. 【量化金融】多因子选股策略
  18. VMware workstation批量创建虚拟机和自动化安装操作系统(二)
  19. ANYbotics /elevation_mapping 配置
  20. 分享五年码农生涯历程经验及2018总结 | 掘金年度征文

热门文章

  1. Node连接MySQL并封装其增删查改
  2. Aptana 添加jQuery提示
  3. Java Code Examples for org.apache.ibatis.annotations.Insert
  4. 查询记录时rs.previous()的使用
  5. 软件定义重划边界——IT就是把复杂东西简单化
  6. Windows Server 2008部署***服务器
  7. 跨库事务处理 spring+hibernate+struts2+jta
  8. Explorer.exe报错故障解决一例
  9. 微软披露25个漏洞 BadAlloc,至少影响数十亿智能设备
  10. 为避免攻击,研究员把严重的比特币漏洞详情焐了两年